merge heads
@@ -147,17 +147,6 @@ limitations under the License.
|
||||
<activity
|
||||
android:label="@string/menu_aggregated_statistics"
|
||||
android:name="com.google.android.apps.mytracks.AggregatedStatsActivity" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name="com.google.android.apps.mytracks.ChartActivity" >
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.TAB" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.default_searchable"
|
||||
android:value="com.google.android.apps.mytracks.SearchActivity" />
|
||||
</activity>
|
||||
<activity android:name="com.google.android.apps.mytracks.ExportActivity"/>
|
||||
<activity
|
||||
android:label="@string/my_tracks_importer"
|
||||
@@ -179,17 +168,6 @@ limitations under the License.
|
||||
<data android:scheme="file" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name="com.google.android.apps.mytracks.MapActivity" >
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.TAB" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.default_searchable"
|
||||
android:value="com.google.android.apps.mytracks.SearchActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:launchMode="singleTop"
|
||||
android:name="com.google.android.apps.mytracks.SearchActivity" >
|
||||
@@ -211,17 +189,6 @@ limitations under the License.
|
||||
<category android:name="android.intent.category.PREFERENCE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name="com.google.android.apps.mytracks.StatsActivity" >
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.TAB" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.default_searchable"
|
||||
android:value="com.google.android.apps.mytracks.SearchActivity" />
|
||||
</activity>
|
||||
<activity android:name="com.google.android.apps.mytracks.TrackDetailActivity" />
|
||||
<activity
|
||||
android:label="@string/track_edit_title"
|
||||
|
||||
@@ -4,8 +4,8 @@ applications that wish to use these files should consult the appropriate
|
||||
licenses or the JAR's authors.
|
||||
|
||||
Android Support Package:
|
||||
android-support-v4.jar
|
||||
- see http://developer.android.com/sdk/compatibility-library.html
|
||||
android-support-v4-r7-googlemaps.jar
|
||||
- see https://github.com/petedoyle/android-support-v4-googlemaps
|
||||
|
||||
Google APIs Client Library for Java:
|
||||
commons-codec-1.3.jar
|
||||
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_window_focused="false"
|
||||
android:drawable="@drawable/left_arrow_chart" />
|
||||
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@drawable/left_arrow_chart_press" />
|
||||
|
||||
<item android:drawable="@drawable/left_arrow_chart" />
|
||||
|
||||
</selector>
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_window_focused="false"
|
||||
android:drawable="@drawable/left_arrow_stats" />
|
||||
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@drawable/left_arrow_stats_press" />
|
||||
|
||||
<item android:drawable="@drawable/left_arrow_stats" />
|
||||
|
||||
</selector>
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_window_focused="false"
|
||||
android:drawable="@drawable/left_arrow_track" />
|
||||
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@drawable/left_arrow_track_press" />
|
||||
|
||||
<item android:drawable="@drawable/left_arrow_track" />
|
||||
|
||||
</selector>
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_window_focused="false"
|
||||
android:drawable="@drawable/right_arrow_chart" />
|
||||
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@drawable/right_arrow_chart_press" />
|
||||
|
||||
<item android:drawable="@drawable/right_arrow_chart" />
|
||||
|
||||
</selector>
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_window_focused="false"
|
||||
android:drawable="@drawable/right_arrow_stats" />
|
||||
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@drawable/right_arrow_stats_press" />
|
||||
|
||||
<item android:drawable="@drawable/right_arrow_stats" />
|
||||
|
||||
</selector>
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_window_focused="false"
|
||||
android:drawable="@drawable/right_arrow_track" />
|
||||
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@drawable/right_arrow_track_press" />
|
||||
|
||||
<item android:drawable="@drawable/right_arrow_track" />
|
||||
|
||||
</selector>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@android:id/tabhost"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent" >
|
||||
<LinearLayout
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical" >
|
||||
<TabWidget
|
||||
android:id="@android:id/tabs"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="horizontal" />
|
||||
<FrameLayout
|
||||
android:id="@android:id/tabcontent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="0"
|
||||
android:layout_width="0dp" />
|
||||
<FrameLayout
|
||||
android:id="@+id/realtabcontent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="match_parent" />
|
||||
</LinearLayout>
|
||||
</TabHost>
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<array name="left_icons">
|
||||
<item>@drawable/left_arrow_chart_stateful</item>
|
||||
<item>@drawable/left_arrow_track_stateful</item>
|
||||
<item>@drawable/left_arrow_stats_stateful</item>
|
||||
</array>
|
||||
|
||||
<array name="right_icons">
|
||||
<item>@drawable/right_arrow_stats_stateful</item>
|
||||
<item>@drawable/right_arrow_chart_stateful</item>
|
||||
<item>@drawable/right_arrow_track_stateful</item>
|
||||
</array>
|
||||
</resources>
|
||||
@@ -2016,6 +2016,10 @@ limitations under the License.
|
||||
</string>
|
||||
|
||||
<!-- Track -->
|
||||
<string name="track_detail_chart_tab"
|
||||
translation_description="In the track detail page, the title of the tab showing the chart.">
|
||||
Chart
|
||||
</string>
|
||||
<string name="track_detail_delete_confirm_message"
|
||||
translation_description="When deleting a track, the confirmation message informing the user that
|
||||
the track and its markers will be permanently deleted from the phone.">
|
||||
@@ -2027,6 +2031,14 @@ limitations under the License.
|
||||
Play Store.">
|
||||
You need Google Earth to play the track. Do you want to install from the Play Store?
|
||||
</string>
|
||||
<string name="track_detail_map_tab"
|
||||
translation_description="In the track detail page, the title of the tab showing the map.">
|
||||
Map
|
||||
</string>
|
||||
<string name="track_detail_stats_tab"
|
||||
translation_description="In the track detail page, the title of the tab showing the stats.">
|
||||
Stats
|
||||
</string>
|
||||
<string name="track_icon_description"
|
||||
translation_description="The accessibility description for the track icon.">
|
||||
Track
|
||||
|
||||
@@ -1,486 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.ChartView.Mode;
|
||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.ZoomControls;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* An activity that displays a chart from the track point provider.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ChartActivity extends Activity implements TrackDataListener {
|
||||
|
||||
private static final int DIALOG_CHART_SETTINGS_ID = 0;
|
||||
|
||||
private final DoubleBuffer elevationBuffer =
|
||||
new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
|
||||
private final DoubleBuffer speedBuffer =
|
||||
new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR);
|
||||
private final ArrayList<double[]> pendingPoints = new ArrayList<double[]>();
|
||||
|
||||
private TrackDataHub dataHub;
|
||||
|
||||
// Stats gathered from received data.
|
||||
private double profileLength = 0;
|
||||
private long startTime = -1;
|
||||
private Location lastLocation;
|
||||
private double trackMaxSpeed;
|
||||
|
||||
// Modes of operation
|
||||
private boolean metricUnits = true;
|
||||
private boolean reportSpeed = true;
|
||||
|
||||
/*
|
||||
* UI elements:
|
||||
*/
|
||||
private ChartView chartView;
|
||||
private LinearLayout busyPane;
|
||||
private ZoomControls zoomControls;
|
||||
|
||||
/**
|
||||
* A runnable that can be posted to the UI thread. It will remove the spinner
|
||||
* (if any), enable/disable zoom controls and orange pointer as appropriate
|
||||
* and redraw.
|
||||
*/
|
||||
private final Runnable updateChart = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Get a local reference in case it's set to null concurrently with this.
|
||||
TrackDataHub localDataHub = dataHub;
|
||||
if (localDataHub == null || isFinishing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
busyPane.setVisibility(View.GONE);
|
||||
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
|
||||
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
|
||||
chartView.setShowPointer(localDataHub.isRecordingSelected());
|
||||
chartView.invalidate();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Log.w(TAG, "ChartActivity.onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
|
||||
setContentView(R.layout.mytracks_charts);
|
||||
ViewGroup layout = (ViewGroup) findViewById(R.id.elevation_chart);
|
||||
chartView = new ChartView(this);
|
||||
LayoutParams params =
|
||||
new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
|
||||
layout.addView(chartView, params);
|
||||
|
||||
busyPane = (LinearLayout) findViewById(R.id.elevation_busypane);
|
||||
zoomControls = (ZoomControls) findViewById(R.id.elevation_zoom);
|
||||
zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
zoomIn();
|
||||
}
|
||||
});
|
||||
zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
zoomOut();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
|
||||
dataHub.registerTrackDataListener(this, EnumSet.of(
|
||||
ListenerDataType.SELECTED_TRACK_CHANGED,
|
||||
ListenerDataType.TRACK_UPDATES,
|
||||
ListenerDataType.POINT_UPDATES,
|
||||
ListenerDataType.SAMPLED_OUT_POINT_UPDATES,
|
||||
ListenerDataType.WAYPOINT_UPDATES,
|
||||
ListenerDataType.DISPLAY_PREFERENCES));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
dataHub.unregisterTrackDataListener(this);
|
||||
dataHub = null;
|
||||
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
private void zoomIn() {
|
||||
chartView.zoomIn();
|
||||
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
|
||||
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
|
||||
}
|
||||
|
||||
private void zoomOut() {
|
||||
chartView.zoomOut();
|
||||
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
|
||||
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
|
||||
}
|
||||
|
||||
private void setMode(Mode newMode) {
|
||||
if (chartView.getMode() != newMode) {
|
||||
chartView.setMode(newMode);
|
||||
dataHub.reloadDataForListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
if (id != DIALOG_CHART_SETTINGS_ID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
View view = getLayoutInflater().inflate(R.layout.chart_settings, null);
|
||||
final RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.chart_settings_x);
|
||||
radioGroup.check(chartView.getMode() == Mode.BY_DISTANCE
|
||||
? R.id.chart_settings_by_distance : R.id.chart_settings_by_time);
|
||||
|
||||
final CheckBox[] checkBoxes = new CheckBox[ChartView.NUM_SERIES];
|
||||
checkBoxes[ChartView.ELEVATION_SERIES] = (CheckBox) view.findViewById(
|
||||
R.id.chart_settings_elevation);
|
||||
checkBoxes[ChartView.SPEED_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_speed);
|
||||
checkBoxes[ChartView.POWER_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_power);
|
||||
checkBoxes[ChartView.CADENCE_SERIES] = (CheckBox) view.findViewById(
|
||||
R.id.chart_settings_cadence);
|
||||
checkBoxes[ChartView.HEART_RATE_SERIES] = (CheckBox) view.findViewById(
|
||||
R.id.chart_settings_heart_rate);
|
||||
|
||||
// set checkboxes values
|
||||
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
|
||||
checkBoxes[i].setChecked(chartView.isChartValueSeriesEnabled(i));
|
||||
}
|
||||
checkBoxes[ChartView.SPEED_SERIES].setText(reportSpeed
|
||||
? R.string.stat_speed : R.string.stat_pace);
|
||||
|
||||
return new AlertDialog.Builder(this)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.generic_cancel, null)
|
||||
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
setMode(radioGroup.getCheckedRadioButtonId() == R.id.chart_settings_by_distance
|
||||
? Mode.BY_DISTANCE
|
||||
: Mode.BY_TIME);
|
||||
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
|
||||
chartView.setChartValueSeriesEnabled(i, checkBoxes[i].isChecked());
|
||||
}
|
||||
chartView.postInvalidate();
|
||||
}
|
||||
})
|
||||
.setTitle(R.string.menu_chart_settings)
|
||||
.setView(view)
|
||||
.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the chart settings dialog
|
||||
*/
|
||||
public void showChartSettingsDialog() {
|
||||
showDialog(DIALOG_CHART_SETTINGS_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a location, creates a new data point for the chart. A data point is
|
||||
* an array double[3 or 6], where:
|
||||
* data[0] = the time or distance
|
||||
* data[1] = the elevation
|
||||
* data[2] = the speed
|
||||
* and possibly:
|
||||
* data[3] = power
|
||||
* data[4] = cadence
|
||||
* data[5] = heart rate
|
||||
*
|
||||
* This must be called in order for each point.
|
||||
*
|
||||
* @param location the location to get data for (this method takes ownership of that location)
|
||||
* @param result the resulting point to fill out
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void fillDataPoint(Location location, double result[]) {
|
||||
double timeOrDistance = Double.NaN,
|
||||
elevation = Double.NaN,
|
||||
speed = Double.NaN,
|
||||
power = Double.NaN,
|
||||
cadence = Double.NaN,
|
||||
heartRate = Double.NaN;
|
||||
|
||||
if (location instanceof MyTracksLocation &&
|
||||
((MyTracksLocation) location).getSensorDataSet() != null) {
|
||||
SensorDataSet sensorData = ((MyTracksLocation) location).getSensorDataSet();
|
||||
if (sensorData.hasPower()
|
||||
&& sensorData.getPower().getState() == Sensor.SensorState.SENDING
|
||||
&& sensorData.getPower().hasValue()) {
|
||||
power = sensorData.getPower().getValue();
|
||||
}
|
||||
if (sensorData.hasCadence()
|
||||
&& sensorData.getCadence().getState() == Sensor.SensorState.SENDING
|
||||
&& sensorData.getCadence().hasValue()) {
|
||||
cadence = sensorData.getCadence().getValue();
|
||||
}
|
||||
if (sensorData.hasHeartRate()
|
||||
&& sensorData.getHeartRate().getState() == Sensor.SensorState.SENDING
|
||||
&& sensorData.getHeartRate().hasValue()) {
|
||||
heartRate = sensorData.getHeartRate().getValue();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Account for segment splits?
|
||||
Mode mode = chartView.getMode();
|
||||
switch (mode) {
|
||||
case BY_DISTANCE:
|
||||
if (lastLocation != null) {
|
||||
double d = lastLocation.distanceTo(location);
|
||||
if (metricUnits) {
|
||||
profileLength += d;
|
||||
} else {
|
||||
profileLength += d * UnitConversions.KM_TO_MI;
|
||||
}
|
||||
}
|
||||
timeOrDistance = profileLength * UnitConversions.M_TO_KM;
|
||||
break;
|
||||
case BY_TIME:
|
||||
if (startTime == -1) {
|
||||
// Base case
|
||||
startTime = location.getTime();
|
||||
}
|
||||
timeOrDistance = (location.getTime() - startTime);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "ChartActivity unknown mode: " + mode);
|
||||
}
|
||||
|
||||
elevationBuffer.setNext(metricUnits
|
||||
? location.getAltitude()
|
||||
: location.getAltitude() * UnitConversions.M_TO_FT);
|
||||
elevation = elevationBuffer.getAverage();
|
||||
|
||||
if (lastLocation == null) {
|
||||
if (Math.abs(location.getSpeed() - 128) > 1) {
|
||||
speedBuffer.setNext(location.getSpeed());
|
||||
}
|
||||
} else if (TripStatisticsBuilder.isValidSpeed(
|
||||
location.getTime(), location.getSpeed(), lastLocation.getTime(),
|
||||
lastLocation.getSpeed(), speedBuffer)
|
||||
&& (location.getSpeed() <= trackMaxSpeed)) {
|
||||
speedBuffer.setNext(location.getSpeed());
|
||||
}
|
||||
speed = speedBuffer.getAverage() * UnitConversions.MS_TO_KMH;
|
||||
if (!metricUnits) {
|
||||
speed *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
if (!reportSpeed) {
|
||||
if (speed != 0) {
|
||||
// Format as hours per unit
|
||||
speed = (60.0 / speed);
|
||||
} else {
|
||||
speed = Double.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep a copy so the location can be reused.
|
||||
lastLocation = location;
|
||||
|
||||
if (result != null) {
|
||||
result[0] = timeOrDistance;
|
||||
result[1] = elevation;
|
||||
result[2] = speed;
|
||||
result[3] = power;
|
||||
result[4] = cadence;
|
||||
result[5] = heartRate;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderStateChange(ProviderState state) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentLocationChanged(Location loc) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentHeadingChanged(double heading) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTrackChanged(Track track, boolean isRecording) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
busyPane.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackUpdated(Track track) {
|
||||
if (track == null || track.getStatistics() == null) {
|
||||
trackMaxSpeed = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
trackMaxSpeed = track.getStatistics().getMaxSpeed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTrackPoints() {
|
||||
profileLength = 0;
|
||||
lastLocation = null;
|
||||
startTime = -1;
|
||||
elevationBuffer.reset();
|
||||
chartView.reset();
|
||||
speedBuffer.reset();
|
||||
pendingPoints.clear();
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
chartView.resetScroll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPoint(Location loc) {
|
||||
if (LocationUtils.isValidLocation(loc)) {
|
||||
double[] point = new double[6];
|
||||
fillDataPoint(loc, point);
|
||||
pendingPoints.add(point);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(Location loc) {
|
||||
// Still account for the point in the smoothing buffers.
|
||||
fillDataPoint(loc, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSegmentSplit() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
chartView.addDataPoints(pendingPoints);
|
||||
pendingPoints.clear();
|
||||
runOnUiThread(updateChart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearWaypoints() {
|
||||
chartView.clearWaypoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypoint(Waypoint wpt) {
|
||||
chartView.addWaypoint(wpt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypointsDone() {
|
||||
runOnUiThread(updateChart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnitsChanged(boolean metric) {
|
||||
if (metricUnits == metric) {
|
||||
return false;
|
||||
}
|
||||
metricUnits = metric;
|
||||
chartView.setMetricUnits(metricUnits);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
chartView.requestLayout();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onReportSpeedChanged(boolean speed) {
|
||||
if (reportSpeed == speed) {
|
||||
return false;
|
||||
}
|
||||
reportSpeed = speed;
|
||||
chartView.setReportSpeed(speed, this);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
chartView.requestLayout();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ChartView getChartView() {
|
||||
return chartView;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setTrackMaxSpeed(double maxSpeed) {
|
||||
trackMaxSpeed = maxSpeed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
/*
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.apps.mytracks.ChartView.Mode;
|
||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ZoomControls;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* A fragment to display track chart to the user.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
// Android reports 128 when the speed is invalid
|
||||
private static final int INVALID_SPEED = 128;
|
||||
private final DoubleBuffer elevationBuffer = new DoubleBuffer(
|
||||
Constants.ELEVATION_SMOOTHING_FACTOR);
|
||||
private final DoubleBuffer speedBuffer = new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR);
|
||||
private final ArrayList<double[]> pendingPoints = new ArrayList<double[]>();
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
|
||||
// Stats gathered from the received data
|
||||
private double totalDistance = 0.0;
|
||||
private long startTime = -1L;
|
||||
private Location lastLocation = null;
|
||||
private double trackMaxSpeed = 0.0;
|
||||
|
||||
// Modes of operation
|
||||
private boolean metricUnits = true;
|
||||
private boolean reportSpeed = true;
|
||||
|
||||
// UI elements
|
||||
private ChartView chartView;
|
||||
private LinearLayout busyPane;
|
||||
private ZoomControls zoomControls;
|
||||
|
||||
/**
|
||||
* A runnable that will remove the spinner (if any), enable/disable zoom
|
||||
* controls and orange pointer as appropriate and redraw.
|
||||
*/
|
||||
private final Runnable updateChart = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (trackDataHub == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
busyPane.setVisibility(View.GONE);
|
||||
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
|
||||
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
|
||||
chartView.setShowPointer(isRecording());
|
||||
chartView.invalidate();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
/*
|
||||
* Create a chartView here to store data thus won't need to reload all the
|
||||
* data on every onStart or onResume.
|
||||
*/
|
||||
chartView = new ChartView(getActivity());
|
||||
};
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.mytracks_charts, container, false);
|
||||
busyPane = (LinearLayout) view.findViewById(R.id.elevation_busypane);
|
||||
zoomControls = (ZoomControls) view.findViewById(R.id.elevation_zoom);
|
||||
zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
zoomIn();
|
||||
}
|
||||
});
|
||||
zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
zoomOut();
|
||||
}
|
||||
});
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
ViewGroup layout = (ViewGroup) getActivity().findViewById(R.id.elevation_chart);
|
||||
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.MATCH_PARENT);
|
||||
layout.addView(chartView, layoutParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
resumeTrackDataHub();
|
||||
getActivity().runOnUiThread(updateChart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
pauseTrackDataHub();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
ViewGroup layout = (ViewGroup) getActivity().findViewById(R.id.elevation_chart);
|
||||
layout.removeView(chartView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the chart view mode.
|
||||
*
|
||||
* @param mode the chart view mode
|
||||
*/
|
||||
public void setMode(Mode mode) {
|
||||
if (chartView.getMode() != mode) {
|
||||
chartView.setMode(mode);
|
||||
reloadTrackDataHub();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the chart view mode.
|
||||
*/
|
||||
public Mode getMode() {
|
||||
return chartView.getMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the chart value series.
|
||||
*
|
||||
* @param index the index of the series
|
||||
* @param enabled true to enable, false to disable
|
||||
*/
|
||||
public void setChartValueSeriesEnabled(int index, boolean enabled) {
|
||||
chartView.setChartValueSeriesEnabled(index, enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the chart value series is enabled.
|
||||
*
|
||||
* @param index the index of the series
|
||||
*/
|
||||
public boolean isChartValueSeriesEnabled(int index) {
|
||||
return chartView.isChartValueSeriesEnabled(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true to report speed instead of pace.
|
||||
*/
|
||||
public boolean isReportSpeed() {
|
||||
return reportSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the chart.
|
||||
*/
|
||||
public void update() {
|
||||
chartView.postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderStateChange(ProviderState state) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentLocationChanged(Location loc) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentHeadingChanged(double heading) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTrackChanged(Track track, boolean isRecording) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
busyPane.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackUpdated(Track track) {
|
||||
if (track == null || track.getStatistics() == null) {
|
||||
trackMaxSpeed = 0.0;
|
||||
return;
|
||||
}
|
||||
trackMaxSpeed = track.getStatistics().getMaxSpeed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTrackPoints() {
|
||||
totalDistance = 0.0;
|
||||
startTime = -1L;
|
||||
lastLocation = null;
|
||||
|
||||
elevationBuffer.reset();
|
||||
speedBuffer.reset();
|
||||
pendingPoints.clear();
|
||||
|
||||
chartView.reset();
|
||||
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
chartView.resetScroll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPoint(Location location) {
|
||||
if (LocationUtils.isValidLocation(location)) {
|
||||
double[] data = new double[6];
|
||||
fillDataPoint(location, data);
|
||||
pendingPoints.add(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(Location location) {
|
||||
if (LocationUtils.isValidLocation(location)) {
|
||||
// Still account for the point in the smoothing buffers.
|
||||
fillDataPoint(location, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSegmentSplit() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
chartView.addDataPoints(pendingPoints);
|
||||
pendingPoints.clear();
|
||||
getActivity().runOnUiThread(updateChart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearWaypoints() {
|
||||
chartView.clearWaypoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypoint(Waypoint waypoint) {
|
||||
if (waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
|
||||
chartView.addWaypoint(waypoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypointsDone() {
|
||||
getActivity().runOnUiThread(updateChart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnitsChanged(boolean metric) {
|
||||
if (metricUnits == metric) {
|
||||
return false;
|
||||
}
|
||||
metricUnits = metric;
|
||||
chartView.setMetricUnits(metricUnits);
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
chartView.requestLayout();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onReportSpeedChanged(boolean speed) {
|
||||
if (reportSpeed == speed) {
|
||||
return false;
|
||||
}
|
||||
reportSpeed = speed;
|
||||
chartView.setReportSpeed(speed, getActivity());
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
chartView.requestLayout();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can be
|
||||
* accessed by multiple threads.
|
||||
*/
|
||||
private synchronized void resumeTrackDataHub() {
|
||||
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
|
||||
trackDataHub.registerTrackDataListener(this, EnumSet.of(
|
||||
ListenerDataType.SELECTED_TRACK_CHANGED,
|
||||
ListenerDataType.TRACK_UPDATES,
|
||||
ListenerDataType.WAYPOINT_UPDATES,
|
||||
ListenerDataType.POINT_UPDATES,
|
||||
ListenerDataType.SAMPLED_OUT_POINT_UPDATES,
|
||||
ListenerDataType.DISPLAY_PREFERENCES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the trackDataHub. Needs to be synchronized because trackDataHub can be
|
||||
* accessed by multiple threads.
|
||||
*/
|
||||
private synchronized void pauseTrackDataHub() {
|
||||
trackDataHub.unregisterTrackDataListener(this);
|
||||
trackDataHub = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if recording. Needs to be synchronized because trackDataHub
|
||||
* can be accessed by multiple threads.
|
||||
*/
|
||||
private synchronized boolean isRecording() {
|
||||
return trackDataHub != null && trackDataHub.isRecordingSelected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the trackDataHub. Needs to be synchronized because trackDataHub can be
|
||||
* accessed by multiple threads.
|
||||
*/
|
||||
private synchronized void reloadTrackDataHub() {
|
||||
if (trackDataHub != null) {
|
||||
trackDataHub.reloadDataForListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To zoom in.
|
||||
*/
|
||||
private void zoomIn() {
|
||||
chartView.zoomIn();
|
||||
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
|
||||
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
|
||||
}
|
||||
|
||||
/**
|
||||
* To zoom out.
|
||||
*/
|
||||
private void zoomOut() {
|
||||
chartView.zoomOut();
|
||||
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
|
||||
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a location, fill in a data point, an array of double[6]. <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>
|
||||
*
|
||||
* @param location the location
|
||||
* @param data the data point to fill in, can be null
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void fillDataPoint(Location location, double data[]) {
|
||||
double timeOrDistance = Double.NaN;
|
||||
double elevation = Double.NaN;
|
||||
double speed = Double.NaN;
|
||||
double power = Double.NaN;
|
||||
double cadence = Double.NaN;
|
||||
double heartRate = Double.NaN;
|
||||
|
||||
// TODO: Use TripStatisticsBuilder
|
||||
if (chartView.getMode() == Mode.BY_DISTANCE) {
|
||||
if (lastLocation != null) {
|
||||
double distance = lastLocation.distanceTo(location) * UnitConversions.M_TO_KM;
|
||||
if (metricUnits) {
|
||||
totalDistance += distance;
|
||||
} else {
|
||||
totalDistance += distance * UnitConversions.KM_TO_MI;
|
||||
}
|
||||
}
|
||||
timeOrDistance = totalDistance;
|
||||
} else {
|
||||
if (startTime == -1L) {
|
||||
startTime = location.getTime();
|
||||
}
|
||||
timeOrDistance = location.getTime() - startTime;
|
||||
}
|
||||
|
||||
elevationBuffer.setNext(metricUnits ? location.getAltitude() : location.getAltitude()
|
||||
* UnitConversions.M_TO_FT);
|
||||
elevation = elevationBuffer.getAverage();
|
||||
|
||||
if (lastLocation == null) {
|
||||
if (Math.abs(location.getSpeed() - INVALID_SPEED) > 1) {
|
||||
speedBuffer.setNext(location.getSpeed());
|
||||
}
|
||||
} else if (TripStatisticsBuilder.isValidSpeed(location.getTime(), location.getSpeed(),
|
||||
lastLocation.getTime(), lastLocation.getSpeed(), speedBuffer)
|
||||
&& (location.getSpeed() <= trackMaxSpeed)) {
|
||||
speedBuffer.setNext(location.getSpeed());
|
||||
}
|
||||
speed = speedBuffer.getAverage() * UnitConversions.MS_TO_KMH;
|
||||
if (!metricUnits) {
|
||||
speed *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
if (!reportSpeed) {
|
||||
speed = 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.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 (data != null) {
|
||||
data[0] = timeOrDistance;
|
||||
data[1] = elevation;
|
||||
data[2] = speed;
|
||||
data[3] = power;
|
||||
data[4] = cadence;
|
||||
data[5] = heartRate;
|
||||
}
|
||||
lastLocation = location;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ChartView getChartView() {
|
||||
return chartView;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setChartView(ChartView view) {
|
||||
chartView = view;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setTrackMaxSpeed(double value) {
|
||||
trackMaxSpeed = value;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setMetricUnits(boolean value) {
|
||||
metricUnits = value;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setReportSpeed(boolean value) {
|
||||
reportSpeed = value;
|
||||
}
|
||||
}
|
||||
@@ -1,558 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.GeoRect;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.google.android.maps.MapController;
|
||||
import com.google.android.maps.MapView;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* The map view activity of the MyTracks application.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class MapActivity extends com.google.android.maps.MapActivity
|
||||
implements View.OnTouchListener, View.OnClickListener,
|
||||
TrackDataListener {
|
||||
|
||||
// Saved instance state keys:
|
||||
// ---------------------------
|
||||
|
||||
private static final String KEY_CURRENT_LOCATION = "currentLocation";
|
||||
private static final String KEY_KEEP_MY_LOCATION_VISIBLE = "keepMyLocationVisible";
|
||||
|
||||
private TrackDataHub dataHub;
|
||||
|
||||
/**
|
||||
* True if the map should be scrolled so that the pointer is always in the
|
||||
* visible area.
|
||||
*/
|
||||
private boolean keepMyLocationVisible;
|
||||
|
||||
/**
|
||||
* True if we've already zoomed into the current location.
|
||||
* Only relevant when {@link #keepMyLocationVisible} is true.
|
||||
*/
|
||||
private boolean myLocationWasZoomedIn;
|
||||
|
||||
/**
|
||||
* The ID of a track on which we want to show a waypoint.
|
||||
* The waypoint will be shown as soon as the track is loaded.
|
||||
*/
|
||||
private long showWaypointTrackId;
|
||||
|
||||
/**
|
||||
* The ID of a waypoint which we want to show.
|
||||
* The waypoint will be shown as soon as its track is loaded.
|
||||
*/
|
||||
private long showWaypointId;
|
||||
|
||||
/**
|
||||
* The track that's currently selected.
|
||||
* This differs from {@link TrackDataHub#getSelectedTrackId} in that this one is only set after
|
||||
* actual track data has been received.
|
||||
*/
|
||||
private long selectedTrackId;
|
||||
|
||||
/**
|
||||
* The current pointer location.
|
||||
* This is kept to quickly center on it when the user requests.
|
||||
*/
|
||||
private Location currentLocation;
|
||||
|
||||
// UI elements:
|
||||
// -------------
|
||||
|
||||
private RelativeLayout screen;
|
||||
private MapView mapView;
|
||||
private MapOverlay mapOverlay;
|
||||
private LinearLayout messagePane;
|
||||
private TextView messageText;
|
||||
private LinearLayout busyPane;
|
||||
|
||||
/**
|
||||
* We are not displaying driving directions. Just an arbitrary track that is
|
||||
* not associated to any licensed mapping data. Therefore it should be okay to
|
||||
* return false here and still comply with the terms of service.
|
||||
*/
|
||||
@Override
|
||||
protected boolean isRouteDisplayed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* We are displaying a location. This needs to return true in order to comply
|
||||
* with the terms of service.
|
||||
*/
|
||||
@Override
|
||||
protected boolean isLocationDisplayed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Application life cycle:
|
||||
// ------------------------
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
Log.d(TAG, "MapActivity.onCreate");
|
||||
super.onCreate(bundle);
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
|
||||
// Inflate the layout:
|
||||
setContentView(R.layout.mytracks_layout);
|
||||
|
||||
// Remove the window's background because the MapView will obscure it
|
||||
getWindow().setBackgroundDrawable(null);
|
||||
|
||||
// Set up a map overlay:
|
||||
screen = (RelativeLayout) findViewById(R.id.screen);
|
||||
mapView = (MapView) findViewById(R.id.map);
|
||||
mapView.requestFocus();
|
||||
mapOverlay = new MapOverlay(this);
|
||||
mapView.getOverlays().add(mapOverlay);
|
||||
mapView.setOnTouchListener(this);
|
||||
mapView.setBuiltInZoomControls(true);
|
||||
messagePane = (LinearLayout) findViewById(R.id.messagepane);
|
||||
messageText = (TextView) findViewById(R.id.messagetext);
|
||||
busyPane = (LinearLayout) findViewById(R.id.busypane);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle bundle) {
|
||||
Log.d(TAG, "MapActivity.onRestoreInstanceState");
|
||||
if (bundle != null) {
|
||||
super.onRestoreInstanceState(bundle);
|
||||
keepMyLocationVisible =
|
||||
bundle.getBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, false);
|
||||
if (bundle.containsKey(KEY_CURRENT_LOCATION)) {
|
||||
currentLocation = (Location) bundle.getParcelable(KEY_CURRENT_LOCATION);
|
||||
if (currentLocation != null) {
|
||||
showCurrentLocation();
|
||||
}
|
||||
} else {
|
||||
currentLocation = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
Log.d(TAG, "MapActivity.onResume");
|
||||
super.onResume();
|
||||
|
||||
dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
|
||||
dataHub.registerTrackDataListener(this, EnumSet.of(
|
||||
ListenerDataType.SELECTED_TRACK_CHANGED,
|
||||
ListenerDataType.POINT_UPDATES,
|
||||
ListenerDataType.WAYPOINT_UPDATES,
|
||||
ListenerDataType.LOCATION_UPDATES,
|
||||
ListenerDataType.COMPASS_UPDATES));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
Log.d(TAG, "MapActivity.onSaveInstanceState");
|
||||
outState.putBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, keepMyLocationVisible);
|
||||
if (currentLocation != null) {
|
||||
outState.putParcelable(KEY_CURRENT_LOCATION, currentLocation);
|
||||
}
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
Log.d(TAG, "MapActivity.onPause");
|
||||
|
||||
dataHub.unregisterTrackDataListener(this);
|
||||
dataHub = null;
|
||||
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a location is visible.
|
||||
*
|
||||
* @param location a given location
|
||||
* @return true if the given location is within the visible map area
|
||||
*/
|
||||
private boolean locationIsVisible(Location location) {
|
||||
if (location == null || mapView == null) {
|
||||
return false;
|
||||
}
|
||||
GeoPoint center = mapView.getMapCenter();
|
||||
int latSpan = mapView.getLatitudeSpan();
|
||||
int lonSpan = mapView.getLongitudeSpan();
|
||||
|
||||
// Bottom of map view is obscured by zoom controls/buttons.
|
||||
// Subtract a margin from the visible area:
|
||||
GeoPoint marginBottom = mapView.getProjection().fromPixels(
|
||||
0, mapView.getHeight());
|
||||
GeoPoint marginTop = mapView.getProjection().fromPixels(0,
|
||||
mapView.getHeight()
|
||||
- mapView.getZoomButtonsController().getZoomControls().getHeight());
|
||||
int margin =
|
||||
Math.abs(marginTop.getLatitudeE6() - marginBottom.getLatitudeE6());
|
||||
GeoRect r = new GeoRect(center, latSpan, lonSpan);
|
||||
r.top += margin;
|
||||
|
||||
GeoPoint geoPoint = LocationUtils.getGeoPoint(location);
|
||||
return r.contains(geoPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Centers (and keeps centered) the map on the current location.
|
||||
*/
|
||||
public void showMyLocation() {
|
||||
dataHub.forceUpdateLocation();
|
||||
keepMyLocationVisible = true;
|
||||
myLocationWasZoomedIn = false;
|
||||
if (currentLocation != null) {
|
||||
showCurrentLocation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the location pointer to the current location and center the map if
|
||||
* the current location is outside the visible area.
|
||||
*/
|
||||
private void showCurrentLocation() {
|
||||
if (mapOverlay == null || mapView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapOverlay.setMyLocation(currentLocation);
|
||||
mapView.postInvalidate();
|
||||
|
||||
if (currentLocation != null && keepMyLocationVisible && !locationIsVisible(currentLocation)) {
|
||||
GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation);
|
||||
MapController controller = mapView.getController();
|
||||
controller.animateTo(geoPoint);
|
||||
if (!myLocationWasZoomedIn && mapView.getZoomLevel() < 18) {
|
||||
// Only zoom in the first time we show the location.
|
||||
myLocationWasZoomedIn = true;
|
||||
controller.setZoom(18);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackUpdated(Track track) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
/**
|
||||
* Zooms and pans the map so that the given track is visible.
|
||||
*
|
||||
* @param track the track
|
||||
*/
|
||||
private void zoomMapToBoundaries(Track track) {
|
||||
if (mapView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (track == null || track.getNumberOfPoints() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
TripStatistics stats = track.getStatistics();
|
||||
int bottom = stats.getBottom();
|
||||
int left = stats.getLeft();
|
||||
int latSpanE6 = stats.getTop() - bottom;
|
||||
int lonSpanE6 = stats.getRight() - left;
|
||||
if (latSpanE6 > 0
|
||||
&& latSpanE6 < 180E6
|
||||
&& lonSpanE6 > 0
|
||||
&& lonSpanE6 < 360E6) {
|
||||
keepMyLocationVisible = false;
|
||||
GeoPoint center = new GeoPoint(
|
||||
bottom + latSpanE6 / 2,
|
||||
left + lonSpanE6 / 2);
|
||||
if (LocationUtils.isValidGeoPoint(center)) {
|
||||
mapView.getController().setCenter(center);
|
||||
mapView.getController().zoomToSpan(latSpanE6, lonSpanE6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zooms and pans the map so that the given waypoint is visible.
|
||||
*/
|
||||
public void showWaypoint(long waypointId) {
|
||||
MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||
Waypoint wpt = providerUtils.getWaypoint(waypointId);
|
||||
if (wpt != null && wpt.getLocation() != null) {
|
||||
keepMyLocationVisible = false;
|
||||
GeoPoint center = new GeoPoint(
|
||||
(int) (wpt.getLocation().getLatitude() * 1E6),
|
||||
(int) (wpt.getLocation().getLongitude() * 1E6));
|
||||
mapView.getController().setCenter(center);
|
||||
mapView.getController().setZoom(20);
|
||||
mapView.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zooms and pans the map so that the given waypoint is visible, when the given track is loaded.
|
||||
* If the track is already loaded, it does that immediately.
|
||||
*
|
||||
* @param trackId the ID of the track on which to show the waypoint
|
||||
* @param waypointId the ID of the waypoint to show
|
||||
*/
|
||||
public void showWaypoint(long trackId, long waypointId) {
|
||||
synchronized (this) {
|
||||
if (trackId == selectedTrackId) {
|
||||
showWaypoint(waypointId);
|
||||
return;
|
||||
}
|
||||
|
||||
showWaypointTrackId = trackId;
|
||||
showWaypointId = waypointId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the proper zooming/panning for a just-loaded track.
|
||||
* This may be either zooming to a waypoint that has been previously selected, or
|
||||
* zooming to the whole track.
|
||||
*
|
||||
* @param track the loaded track
|
||||
*/
|
||||
private void zoomLoadedTrack(Track track) {
|
||||
synchronized (this) {
|
||||
if (track.getId() == showWaypointTrackId) {
|
||||
// There's a waypoint to show in this track.
|
||||
showWaypoint(showWaypointId);
|
||||
|
||||
showWaypointId = 0L;
|
||||
showWaypointTrackId = 0L;
|
||||
} else {
|
||||
// Zoom out to show the whole track.
|
||||
zoomMapToBoundaries(track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTrackChanged(final Track track, final boolean isRecording) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean trackSelected = track != null;
|
||||
mapOverlay.setTrackDrawingEnabled(trackSelected);
|
||||
|
||||
if (trackSelected) {
|
||||
busyPane.setVisibility(View.VISIBLE);
|
||||
|
||||
synchronized (this) {
|
||||
// Need to get the track ID only at this point, to prevent a race condition
|
||||
// among showWaypoint, zoomLoadedTrack and dataHub.loadTrack.
|
||||
selectedTrackId = track.getId();
|
||||
zoomLoadedTrack(track);
|
||||
}
|
||||
|
||||
mapOverlay.setShowEndMarker(!isRecording);
|
||||
busyPane.setVisibility(View.GONE);
|
||||
}
|
||||
mapView.invalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the map is currently in satellite view mode.
|
||||
*/
|
||||
public boolean isSatelliteView() {
|
||||
return mapView.isSatellite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes whether the map should be in satellite view mode.
|
||||
*/
|
||||
public void setSatelliteView(boolean sat) {
|
||||
mapView.setSatellite(sat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v == messagePane) {
|
||||
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We want the pointer to become visible again in case of the next location
|
||||
* update:
|
||||
*/
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent event) {
|
||||
if (keepMyLocationVisible && event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
if (!locationIsVisible(currentLocation)) {
|
||||
keepMyLocationVisible = false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderStateChange(ProviderState state) {
|
||||
final int messageId;
|
||||
final boolean isGpsDisabled;
|
||||
switch (state) {
|
||||
case DISABLED:
|
||||
messageId = R.string.gps_need_to_enable;
|
||||
isGpsDisabled = true;
|
||||
break;
|
||||
case NO_FIX:
|
||||
case BAD_FIX:
|
||||
messageId = R.string.gps_wait_for_fix;
|
||||
isGpsDisabled = false;
|
||||
break;
|
||||
case GOOD_FIX:
|
||||
// Nothing to show.
|
||||
messageId = -1;
|
||||
isGpsDisabled = false;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected state: " + state);
|
||||
}
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (messageId != -1) {
|
||||
messageText.setText(messageId);
|
||||
messagePane.setVisibility(View.VISIBLE);
|
||||
|
||||
if (isGpsDisabled) {
|
||||
// Give a warning about this state.
|
||||
Toast.makeText(MapActivity.this,
|
||||
R.string.gps_not_found,
|
||||
Toast.LENGTH_LONG).show();
|
||||
|
||||
// Make clicking take the user to the location settings.
|
||||
messagePane.setOnClickListener(MapActivity.this);
|
||||
} else {
|
||||
messagePane.setOnClickListener(null);
|
||||
}
|
||||
} else {
|
||||
messagePane.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
screen.requestLayout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentLocationChanged(Location location) {
|
||||
currentLocation = location;
|
||||
showCurrentLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentHeadingChanged(double heading) {
|
||||
synchronized (this) {
|
||||
if (mapOverlay.setHeading((float) heading)) {
|
||||
mapView.postInvalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearWaypoints() {
|
||||
mapOverlay.clearWaypoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypoint(Waypoint waypoint) {
|
||||
if (LocationUtils.isValidLocation(waypoint.getLocation())) {
|
||||
// TODO: Optimize locking inside addWaypoint
|
||||
mapOverlay.addWaypoint(waypoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypointsDone() {
|
||||
mapView.postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTrackPoints() {
|
||||
mapOverlay.clearPoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPoint(Location loc) {
|
||||
mapOverlay.addLocation(loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSegmentSplit() {
|
||||
mapOverlay.addSegmentSplit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(Location loc) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
mapView.postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnitsChanged(boolean metric) {
|
||||
// We don't care.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onReportSpeedChanged(boolean reportSpeed) {
|
||||
// We don't care.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,520 @@
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.GeoRect;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.google.android.maps.MapController;
|
||||
import com.google.android.maps.MapView;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* A fragment to display map to the user.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class MapFragment extends Fragment
|
||||
implements View.OnTouchListener, View.OnClickListener, TrackDataListener {
|
||||
|
||||
private static final String KEY_CURRENT_LOCATION = "currentLocation";
|
||||
private static final String KEY_KEEP_MY_LOCATION_VISIBLE = "keepMyLocationVisible";
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
|
||||
// True to keep my location visible.
|
||||
private boolean keepMyLocationVisible;
|
||||
|
||||
// True to zoom to my location. Only apply when keepMyLocationVisible is true.
|
||||
private boolean zoomToMyLocation;
|
||||
|
||||
// The track id of the waypoint to show.
|
||||
private long waypointTrackId;
|
||||
|
||||
// The waypoint id to show
|
||||
private long waypointId;
|
||||
|
||||
// The current selected track id. Set in onSelectedTrackChanged.
|
||||
private long currentSelectedTrackId;
|
||||
|
||||
// The current location. Set in onCurrentLocationChanged.
|
||||
private Location currentLocation;
|
||||
|
||||
// UI elements
|
||||
private View mapViewContainer;
|
||||
private MapOverlay mapOverlay;
|
||||
private RelativeLayout screen;
|
||||
private MapView mapView;
|
||||
private LinearLayout messagePane;
|
||||
private TextView messageText;
|
||||
private LinearLayout busyPane;
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
mapViewContainer = ((TrackDetailActivity) getActivity()).getMapViewContainer();
|
||||
|
||||
mapOverlay = new MapOverlay(getActivity());
|
||||
|
||||
screen = (RelativeLayout) mapViewContainer.findViewById(R.id.screen);
|
||||
mapView = (MapView) mapViewContainer.findViewById(R.id.map);
|
||||
mapView.requestFocus();
|
||||
mapView.setOnTouchListener(this);
|
||||
mapView.setBuiltInZoomControls(true);
|
||||
mapView.getOverlays().add(mapOverlay);
|
||||
messagePane = (LinearLayout) mapViewContainer.findViewById(R.id.messagepane);
|
||||
messageText = (TextView) mapViewContainer.findViewById(R.id.messagetext);
|
||||
busyPane = (LinearLayout) mapViewContainer.findViewById(R.id.busypane);
|
||||
|
||||
return mapViewContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
if (savedInstanceState != null) {
|
||||
keepMyLocationVisible = savedInstanceState.getBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, false);
|
||||
currentLocation = (Location) savedInstanceState.getParcelable(KEY_CURRENT_LOCATION);
|
||||
if (currentLocation != null) {
|
||||
updateCurrentLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
resumeTrackDataHub();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, keepMyLocationVisible);
|
||||
if (currentLocation != null) {
|
||||
outState.putParcelable(KEY_CURRENT_LOCATION, currentLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
pauseTrackDataHub();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
ViewGroup parentViewGroup = (ViewGroup) mapViewContainer.getParent();
|
||||
if (parentViewGroup != null) {
|
||||
parentViewGroup.removeView(mapViewContainer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows my location.
|
||||
*/
|
||||
public void showMyLocation() {
|
||||
updateTrackDataHub();
|
||||
keepMyLocationVisible = true;
|
||||
zoomToMyLocation = true;
|
||||
if (currentLocation != null) {
|
||||
updateCurrentLocation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the waypoint.
|
||||
*
|
||||
* @param id the waypoint id
|
||||
*/
|
||||
public void showWaypoint(long id) {
|
||||
MyTracksProviderUtils MyTracksProviderUtils = Factory.get(getActivity());
|
||||
Waypoint waypoint = MyTracksProviderUtils.getWaypoint(id);
|
||||
if (waypoint != null && waypoint.getLocation() != null) {
|
||||
keepMyLocationVisible = false;
|
||||
GeoPoint center = new GeoPoint((int) (waypoint.getLocation().getLatitude() * 1E6),
|
||||
(int) (waypoint.getLocation().getLongitude() * 1E6));
|
||||
mapView.getController().setCenter(center);
|
||||
mapView.getController().setZoom(mapView.getMaxZoomLevel());
|
||||
mapView.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the waypoint.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param id the waypoint id
|
||||
*/
|
||||
public void showWaypoint(long trackId, long id) {
|
||||
/*
|
||||
* Synchronize to prevent race condition in changing waypointTrackId and
|
||||
* waypointId variables.
|
||||
*/
|
||||
synchronized (this) {
|
||||
if (trackId == currentSelectedTrackId) {
|
||||
showWaypoint(id);
|
||||
waypointTrackId = -1L;
|
||||
waypointId = -1L;
|
||||
return;
|
||||
}
|
||||
waypointTrackId = trackId;
|
||||
waypointId = id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if in satellite mode.
|
||||
*/
|
||||
public boolean isSatelliteView() {
|
||||
return mapView.isSatellite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the satellite mode
|
||||
*
|
||||
* @param enabled true for satellite mode, false for map mode
|
||||
*/
|
||||
public void setSatelliteView(boolean enabled) {
|
||||
mapView.setSatellite(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent event) {
|
||||
if (keepMyLocationVisible && event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
if (!isVisible(currentLocation)) {
|
||||
/*
|
||||
* Only set to false when no longer visible. Thus can keep showing the
|
||||
* current location with the next location update.
|
||||
*/
|
||||
keepMyLocationVisible = false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v == messagePane) {
|
||||
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderStateChange(ProviderState state) {
|
||||
final int messageId;
|
||||
final boolean isGpsDisabled;
|
||||
switch (state) {
|
||||
case DISABLED:
|
||||
messageId = R.string.gps_need_to_enable;
|
||||
isGpsDisabled = true;
|
||||
break;
|
||||
case NO_FIX:
|
||||
case BAD_FIX:
|
||||
messageId = R.string.gps_wait_for_fix;
|
||||
isGpsDisabled = false;
|
||||
break;
|
||||
case GOOD_FIX:
|
||||
messageId = -1;
|
||||
isGpsDisabled = false;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected state: " + state);
|
||||
}
|
||||
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (messageId != -1) {
|
||||
messageText.setText(messageId);
|
||||
messagePane.setVisibility(View.VISIBLE);
|
||||
|
||||
if (isGpsDisabled) {
|
||||
Toast.makeText(getActivity(), R.string.gps_not_found, Toast.LENGTH_LONG).show();
|
||||
|
||||
// Click to show the location source settings
|
||||
messagePane.setOnClickListener(MapFragment.this);
|
||||
} else {
|
||||
messagePane.setOnClickListener(null);
|
||||
}
|
||||
} else {
|
||||
messagePane.setVisibility(View.GONE);
|
||||
}
|
||||
screen.requestLayout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentLocationChanged(Location location) {
|
||||
currentLocation = location;
|
||||
updateCurrentLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentHeadingChanged(double heading) {
|
||||
if (mapOverlay.setHeading((float) heading)) {
|
||||
mapView.postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTrackChanged(final Track track, final boolean isRecording) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean hasTrack = track != null;
|
||||
mapOverlay.setTrackDrawingEnabled(hasTrack);
|
||||
|
||||
if (hasTrack) {
|
||||
busyPane.setVisibility(View.VISIBLE);
|
||||
|
||||
synchronized (this) {
|
||||
/*
|
||||
* Synchronize to prevent race condition in changing waypointTrackId
|
||||
* and waypointId variables.
|
||||
*/
|
||||
currentSelectedTrackId = track.getId();
|
||||
updateMap(track);
|
||||
}
|
||||
mapOverlay.setShowEndMarker(!isRecording);
|
||||
busyPane.setVisibility(View.GONE);
|
||||
}
|
||||
mapView.invalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackUpdated(Track track) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTrackPoints() {
|
||||
mapOverlay.clearPoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPoint(Location location) {
|
||||
if (LocationUtils.isValidLocation(location)) {
|
||||
mapOverlay.addLocation(location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(Location loc) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSegmentSplit() {
|
||||
mapOverlay.addSegmentSplit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
mapView.postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearWaypoints() {
|
||||
mapOverlay.clearWaypoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypoint(Waypoint waypoint) {
|
||||
if (waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
|
||||
// TODO: Optimize locking inside addWaypoint
|
||||
mapOverlay.addWaypoint(waypoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypointsDone() {
|
||||
mapView.postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnitsChanged(boolean metric) {
|
||||
// We don't care.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onReportSpeedChanged(boolean reportSpeed) {
|
||||
// We don't care.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can be
|
||||
* accessed by multiple threads.
|
||||
*/
|
||||
private synchronized void resumeTrackDataHub() {
|
||||
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
|
||||
trackDataHub.registerTrackDataListener(this, EnumSet.of(
|
||||
ListenerDataType.SELECTED_TRACK_CHANGED,
|
||||
ListenerDataType.WAYPOINT_UPDATES,
|
||||
ListenerDataType.POINT_UPDATES,
|
||||
ListenerDataType.LOCATION_UPDATES,
|
||||
ListenerDataType.COMPASS_UPDATES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the trackDataHub. Needs to be synchronized because trackDataHub can be
|
||||
* accessed by multiple threads.
|
||||
*/
|
||||
private synchronized void pauseTrackDataHub() {
|
||||
trackDataHub.unregisterTrackDataListener(this);
|
||||
trackDataHub = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the trackDataHub. Needs to be synchronized because trackDataHub can be
|
||||
* accessed by multiple threads.
|
||||
*/
|
||||
private synchronized void updateTrackDataHub() {
|
||||
if (trackDataHub != null) {
|
||||
trackDataHub.forceUpdateLocation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the map by either zooming to the requested waypoint or showing the track.
|
||||
*
|
||||
* @param track the track
|
||||
*/
|
||||
private void updateMap(Track track) {
|
||||
if (track.getId() == waypointTrackId) {
|
||||
// Show the waypoint
|
||||
showWaypoint(waypointId);
|
||||
|
||||
waypointTrackId = -1L;
|
||||
waypointId = -1L;
|
||||
} else {
|
||||
// Show the track
|
||||
showTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the location is visible.
|
||||
*
|
||||
* @param location the location
|
||||
*/
|
||||
private boolean isVisible(Location location) {
|
||||
if (location == null || mapView == null) {
|
||||
return false;
|
||||
}
|
||||
GeoPoint mapCenter = mapView.getMapCenter();
|
||||
int latitudeSpan = mapView.getLatitudeSpan();
|
||||
int longitudeSpan = mapView.getLongitudeSpan();
|
||||
|
||||
/*
|
||||
* The bottom of the mapView is obscured by the zoom controls, subtract its
|
||||
* height from the visible area.
|
||||
*/
|
||||
GeoPoint zoomControlBottom = mapView.getProjection().fromPixels(0, mapView.getHeight());
|
||||
GeoPoint zoomControlTop = mapView.getProjection().fromPixels(
|
||||
0, mapView.getHeight() - mapView.getZoomButtonsController().getZoomControls().getHeight());
|
||||
int zoomControlMargin = Math.abs(zoomControlTop.getLatitudeE6()
|
||||
- zoomControlBottom.getLatitudeE6());
|
||||
GeoRect geoRect = new GeoRect(mapCenter, latitudeSpan, longitudeSpan);
|
||||
geoRect.top += zoomControlMargin;
|
||||
|
||||
GeoPoint geoPoint = LocationUtils.getGeoPoint(location);
|
||||
return geoRect.contains(geoPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current location and centers it if necessary.
|
||||
*/
|
||||
private void updateCurrentLocation() {
|
||||
if (mapOverlay == null || mapView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapOverlay.setMyLocation(currentLocation);
|
||||
mapView.postInvalidate();
|
||||
|
||||
if (currentLocation != null && keepMyLocationVisible && !isVisible(currentLocation)) {
|
||||
GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation);
|
||||
MapController mapController = mapView.getController();
|
||||
mapController.animateTo(geoPoint);
|
||||
if (zoomToMyLocation) {
|
||||
// Only zoom in the first time we show the location.
|
||||
zoomToMyLocation = false;
|
||||
if (mapView.getZoomLevel() < mapView.getMaxZoomLevel()) {
|
||||
mapController.setZoom(mapView.getMaxZoomLevel());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the track.
|
||||
*
|
||||
* @param track the track
|
||||
*/
|
||||
private void showTrack(Track track) {
|
||||
if (mapView == null || track == null || track.getNumberOfPoints() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
TripStatistics tripStatistics = track.getStatistics();
|
||||
int bottom = tripStatistics.getBottom();
|
||||
int left = tripStatistics.getLeft();
|
||||
int latitudeSpanE6 = tripStatistics.getTop() - bottom;
|
||||
int longitudeSpanE6 = tripStatistics.getRight() - left;
|
||||
if (latitudeSpanE6 > 0 && latitudeSpanE6 < 180E6 && longitudeSpanE6 > 0
|
||||
&& longitudeSpanE6 < 360E6) {
|
||||
keepMyLocationVisible = false;
|
||||
GeoPoint center = new GeoPoint(bottom + latitudeSpanE6 / 2, left + longitudeSpanE6 / 2);
|
||||
if (LocationUtils.isValidGeoPoint(center)) {
|
||||
mapView.getController().setCenter(center);
|
||||
mapView.getController().zoomToSpan(latitudeSpanE6, longitudeSpanE6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.RelativeLayout.LayoutParams;
|
||||
|
||||
/**
|
||||
* Creates previous and next arrows for a given activity.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class NavControls {
|
||||
|
||||
private static final int KEEP_VISIBLE_MILLIS = 4000;
|
||||
private static final boolean FADE_CONTROLS = true;
|
||||
|
||||
private static final Animation SHOW_NEXT_ANIMATION =
|
||||
new AlphaAnimation(0F, 1F);
|
||||
private static final Animation HIDE_NEXT_ANIMATION =
|
||||
new AlphaAnimation(1F, 0F);
|
||||
private static final Animation SHOW_PREV_ANIMATION =
|
||||
new AlphaAnimation(0F, 1F);
|
||||
private static final Animation HIDE_PREV_ANIMATION =
|
||||
new AlphaAnimation(1F, 0F);
|
||||
|
||||
/**
|
||||
* A touchable image view.
|
||||
* When touched it changes the navigation control icons accordingly.
|
||||
*/
|
||||
private class TouchLayout extends RelativeLayout implements Runnable {
|
||||
private final boolean isLeft;
|
||||
private final ImageView icon;
|
||||
|
||||
public TouchLayout(Context context, boolean isLeft) {
|
||||
super(context);
|
||||
this.isLeft = isLeft;
|
||||
this.icon = new ImageView(context);
|
||||
icon.setVisibility(View.GONE);
|
||||
addView(icon);
|
||||
}
|
||||
|
||||
public void setIcon(Drawable drawable) {
|
||||
icon.setImageDrawable(drawable);
|
||||
icon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
setPressed(true);
|
||||
shiftIcons(isLeft);
|
||||
|
||||
// Call the user back
|
||||
handler.post(this);
|
||||
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
setPressed(false);
|
||||
break;
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
touchRunnable.run();
|
||||
setPressed(false);
|
||||
}
|
||||
}
|
||||
|
||||
private final Handler handler = new Handler();
|
||||
private final Runnable dismissControls = new Runnable() {
|
||||
public void run() {
|
||||
hide();
|
||||
}
|
||||
};
|
||||
|
||||
private final TouchLayout prevImage;
|
||||
private final TouchLayout nextImage;
|
||||
private final TypedArray leftIcons;
|
||||
private final TypedArray rightIcons;
|
||||
private final Runnable touchRunnable;
|
||||
|
||||
private boolean isVisible = false;
|
||||
private int currentIcons;
|
||||
|
||||
public NavControls(Context context, ViewGroup container,
|
||||
TypedArray leftIcons, TypedArray rightIcons,
|
||||
Runnable touchRunnable) {
|
||||
this.leftIcons = leftIcons;
|
||||
this.rightIcons = rightIcons;
|
||||
this.touchRunnable = touchRunnable;
|
||||
|
||||
if (leftIcons.length() != rightIcons.length() || leftIcons.length() < 1) {
|
||||
throw new IllegalArgumentException("Invalid icons specified");
|
||||
}
|
||||
if (touchRunnable == null) {
|
||||
throw new NullPointerException("Runnable cannot be null");
|
||||
}
|
||||
|
||||
LayoutParams prevParams = new LayoutParams(
|
||||
LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
LayoutParams nextParams = new LayoutParams(
|
||||
LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
|
||||
prevParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
|
||||
nextParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
|
||||
prevParams.addRule(RelativeLayout.CENTER_VERTICAL);
|
||||
nextParams.addRule(RelativeLayout.CENTER_VERTICAL);
|
||||
|
||||
nextImage = new TouchLayout(context, false);
|
||||
prevImage = new TouchLayout(context, true);
|
||||
nextImage.setLayoutParams(nextParams);
|
||||
prevImage.setLayoutParams(prevParams);
|
||||
nextImage.setVisibility(View.INVISIBLE);
|
||||
prevImage.setVisibility(View.INVISIBLE);
|
||||
|
||||
container.addView(prevImage);
|
||||
container.addView(nextImage);
|
||||
|
||||
prevImage.setIcon(leftIcons.getDrawable(0));
|
||||
nextImage.setIcon(rightIcons.getDrawable(0));
|
||||
this.currentIcons = 0;
|
||||
}
|
||||
|
||||
private void keepVisible() {
|
||||
if (isVisible && FADE_CONTROLS) {
|
||||
handler.removeCallbacks(dismissControls);
|
||||
handler.postDelayed(dismissControls, KEEP_VISIBLE_MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (!isVisible) {
|
||||
SHOW_PREV_ANIMATION.setDuration(500);
|
||||
SHOW_PREV_ANIMATION.startNow();
|
||||
prevImage.setPressed(false);
|
||||
prevImage.setAnimation(SHOW_PREV_ANIMATION);
|
||||
prevImage.setVisibility(View.VISIBLE);
|
||||
|
||||
SHOW_NEXT_ANIMATION.setDuration(500);
|
||||
SHOW_NEXT_ANIMATION.startNow();
|
||||
nextImage.setPressed(false);
|
||||
nextImage.setAnimation(SHOW_NEXT_ANIMATION);
|
||||
nextImage.setVisibility(View.VISIBLE);
|
||||
|
||||
isVisible = true;
|
||||
keepVisible();
|
||||
} else {
|
||||
keepVisible();
|
||||
}
|
||||
}
|
||||
|
||||
public void hideNow() {
|
||||
handler.removeCallbacks(dismissControls);
|
||||
isVisible = false;
|
||||
prevImage.clearAnimation();
|
||||
prevImage.setVisibility(View.INVISIBLE);
|
||||
nextImage.clearAnimation();
|
||||
nextImage.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
isVisible = false;
|
||||
prevImage.setAnimation(HIDE_PREV_ANIMATION);
|
||||
HIDE_PREV_ANIMATION.setDuration(500);
|
||||
HIDE_PREV_ANIMATION.startNow();
|
||||
prevImage.setVisibility(View.INVISIBLE);
|
||||
|
||||
nextImage.setAnimation(HIDE_NEXT_ANIMATION);
|
||||
HIDE_NEXT_ANIMATION.setDuration(500);
|
||||
HIDE_NEXT_ANIMATION.startNow();
|
||||
nextImage.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
public int getCurrentIcons() {
|
||||
return currentIcons;
|
||||
}
|
||||
|
||||
private void shiftIcons(boolean isLeft) {
|
||||
// Increment or decrement by one, with wrap around
|
||||
currentIcons = (currentIcons + leftIcons.length() +
|
||||
(isLeft ? -1 : 1)) % leftIcons.length();
|
||||
prevImage.setIcon(leftIcons.getDrawable(currentIcons));
|
||||
nextImage.setIcon(rightIcons.getDrawable(currentIcons));
|
||||
}
|
||||
}
|
||||
@@ -1,320 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.services.ServiceUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* An activity that displays track statistics to the user.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class StatsActivity extends Activity implements TrackDataListener {
|
||||
/**
|
||||
* A runnable for posting to the UI thread. Will update the total time field.
|
||||
*/
|
||||
private final Runnable updateResults = new Runnable() {
|
||||
public void run() {
|
||||
if (dataHub != null && dataHub.isRecordingSelected()) {
|
||||
utils.setTime(R.id.total_time_register,
|
||||
System.currentTimeMillis() - startTime);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private StatsUtilities utils;
|
||||
private UIUpdateThread thread;
|
||||
|
||||
/**
|
||||
* The start time of the selected track.
|
||||
*/
|
||||
private long startTime = -1;
|
||||
|
||||
private TrackDataHub dataHub;
|
||||
private SharedPreferences preferences;
|
||||
|
||||
/**
|
||||
* A thread that updates the total time field every second.
|
||||
*/
|
||||
private class UIUpdateThread extends Thread {
|
||||
|
||||
public UIUpdateThread() {
|
||||
super();
|
||||
Log.i(TAG, "Created UI update thread");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(TAG, "Started UI update thread");
|
||||
while (ServiceUtils.isRecording(StatsActivity.this, null, preferences)) {
|
||||
runOnUiThread(updateResults);
|
||||
try {
|
||||
Thread.sleep(1000L);
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "StatsActivity: Caught exception on sleep.", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log.w(TAG, "UIUpdateThread finished.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
preferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
utils = new StatsUtilities(this);
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
|
||||
setContentView(R.layout.stats);
|
||||
|
||||
ScrollView sv = ((ScrollView) findViewById(R.id.scrolly));
|
||||
sv.setScrollBarStyle(ScrollView.SCROLLBARS_OUTSIDE_INSET);
|
||||
|
||||
showUnknownLocation();
|
||||
updateLabels();
|
||||
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
if (metrics.heightPixels > 600) {
|
||||
((TextView) findViewById(R.id.speed_register)).setTextSize(80.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
|
||||
dataHub.registerTrackDataListener(this, EnumSet.of(
|
||||
ListenerDataType.SELECTED_TRACK_CHANGED,
|
||||
ListenerDataType.TRACK_UPDATES,
|
||||
ListenerDataType.LOCATION_UPDATES,
|
||||
ListenerDataType.DISPLAY_PREFERENCES));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
dataHub.unregisterTrackDataListener(this);
|
||||
dataHub = null;
|
||||
|
||||
if (thread != null) {
|
||||
thread.interrupt();
|
||||
thread = null;
|
||||
}
|
||||
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnitsChanged(boolean metric) {
|
||||
if (utils.isMetricUnits() == metric) {
|
||||
return false;
|
||||
}
|
||||
utils.setMetricUnits(metric);
|
||||
updateLabels();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onReportSpeedChanged(boolean speed) {
|
||||
if (utils.isReportSpeed() == speed) {
|
||||
return false;
|
||||
}
|
||||
utils.setReportSpeed(speed);
|
||||
updateLabels();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateLabels() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
utils.updateUnits();
|
||||
utils.setSpeedLabel(R.id.speed_label, R.string.stat_speed, R.string.stat_pace);
|
||||
utils.setSpeedLabels();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given location fields (latitude, longitude, altitude) and all
|
||||
* other fields.
|
||||
*
|
||||
* @param l may be null (will set location fields to unknown)
|
||||
*/
|
||||
private void showLocation(Location l) {
|
||||
utils.setAltitude(R.id.elevation_register, l.getAltitude());
|
||||
utils.setLatLong(R.id.latitude_register, l.getLatitude());
|
||||
utils.setLatLong(R.id.longitude_register, l.getLongitude());
|
||||
utils.setSpeed(R.id.speed_register, l.getSpeed() * UnitConversions.MS_TO_KMH);
|
||||
}
|
||||
|
||||
private void showUnknownLocation() {
|
||||
utils.setUnknown(R.id.elevation_register);
|
||||
utils.setUnknown(R.id.latitude_register);
|
||||
utils.setUnknown(R.id.longitude_register);
|
||||
utils.setUnknown(R.id.speed_register);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTrackChanged(Track track, boolean isRecording) {
|
||||
/*
|
||||
* Checks if this activity needs to update live track data or not.
|
||||
* If so, make sure that:
|
||||
* a) a thread keeps updating the total time
|
||||
* b) a location listener is registered
|
||||
* c) a content observer is registered
|
||||
* Otherwise unregister listeners, observers, and kill update thread.
|
||||
*/
|
||||
final boolean startThread = (thread == null) && isRecording;
|
||||
final boolean killThread = (thread != null) && (!isRecording);
|
||||
if (startThread) {
|
||||
thread = new UIUpdateThread();
|
||||
thread.start();
|
||||
} else if (killThread) {
|
||||
thread.interrupt();
|
||||
thread = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentLocationChanged(final Location loc) {
|
||||
TrackDataHub localDataHub = dataHub;
|
||||
if (localDataHub != null && localDataHub.isRecordingSelected()) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (loc != null) {
|
||||
showLocation(loc);
|
||||
} else {
|
||||
showUnknownLocation();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentHeadingChanged(double heading) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderStateChange(ProviderState state) {
|
||||
switch (state) {
|
||||
case DISABLED:
|
||||
case NO_FIX:
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
showUnknownLocation();
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackUpdated(final Track track) {
|
||||
TrackDataHub localDataHub = dataHub;
|
||||
final boolean recordingSelected = localDataHub != null && localDataHub.isRecordingSelected();
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (track == null || track.getStatistics() == null) {
|
||||
utils.setAllToUnknown();
|
||||
return;
|
||||
}
|
||||
|
||||
startTime = track.getStatistics().getStartTime();
|
||||
if (!recordingSelected) {
|
||||
utils.setTime(R.id.total_time_register,
|
||||
track.getStatistics().getTotalTime());
|
||||
showUnknownLocation();
|
||||
}
|
||||
utils.setAllStats(track.getStatistics());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearWaypoints() {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypoint(Waypoint wpt) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypointsDone() {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTrackPoints() {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPoint(Location loc) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSegmentSplit() {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(Location loc) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
// We don't care.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.services.ServiceUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* A fragment to display track statistics to the user.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
private static final String TAG = StatsFragment.class.getSimpleName();
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
private StatsUtilities statsUtilities;
|
||||
private TrackDataHub trackDataHub;
|
||||
private UiUpdateThread uiUpdateThread;
|
||||
|
||||
// The start time of the current track.
|
||||
private long startTime = -1L;
|
||||
|
||||
// A runnable to update the total time field.
|
||||
private final Runnable updateTotalTime = new Runnable() {
|
||||
public void run() {
|
||||
if (isRecording()) {
|
||||
statsUtilities.setTime(R.id.total_time_register, System.currentTimeMillis() - startTime);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A thread that updates the total time field every second.
|
||||
*/
|
||||
private class UiUpdateThread extends Thread {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(TAG, "UI update thread started");
|
||||
while (ServiceUtils.isRecording(getActivity(), null, sharedPreferences)) {
|
||||
getActivity().runOnUiThread(updateTotalTime);
|
||||
try {
|
||||
Thread.sleep(1000L);
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(TAG, "UI update thread caught exception", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "UI update thread finished");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
sharedPreferences = getActivity()
|
||||
.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
statsUtilities = new StatsUtilities(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.stats, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
ScrollView scrollView = (ScrollView) getActivity().findViewById(R.id.scrolly);
|
||||
scrollView.setScrollBarStyle(ScrollView.SCROLLBARS_OUTSIDE_INSET);
|
||||
updateLabels();
|
||||
setLocationUnknown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
resumeTrackDataHub();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
pauseTrackDataHub();
|
||||
if (uiUpdateThread != null) {
|
||||
uiUpdateThread.interrupt();
|
||||
uiUpdateThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderStateChange(ProviderState state) {
|
||||
if (state == ProviderState.DISABLED || state == ProviderState.NO_FIX) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setLocationUnknown();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentLocationChanged(final Location location) {
|
||||
if (isRecording()) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (location != null) {
|
||||
setLocation(location);
|
||||
} else {
|
||||
setLocationUnknown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentHeadingChanged(double heading) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectedTrackChanged(Track track, boolean isRecording) {
|
||||
if (uiUpdateThread == null && isRecording) {
|
||||
uiUpdateThread = new UiUpdateThread();
|
||||
uiUpdateThread.start();
|
||||
} else if (uiUpdateThread != null && !isRecording) {
|
||||
uiUpdateThread.interrupt();
|
||||
uiUpdateThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackUpdated(final Track track) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (track == null || track.getStatistics() == null) {
|
||||
statsUtilities.setAllToUnknown();
|
||||
return;
|
||||
}
|
||||
|
||||
startTime = track.getStatistics().getStartTime();
|
||||
if (!isRecording()) {
|
||||
statsUtilities.setTime(R.id.total_time_register, track.getStatistics().getTotalTime());
|
||||
setLocationUnknown();
|
||||
}
|
||||
statsUtilities.setAllStats(track.getStatistics());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTrackPoints() {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPoint(Location loc) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(Location loc) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSegmentSplit() {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearWaypoints() {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypoint(Waypoint wpt) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypointsDone() {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnitsChanged(boolean metric) {
|
||||
if (statsUtilities.isMetricUnits() == metric) {
|
||||
return false;
|
||||
}
|
||||
statsUtilities.setMetricUnits(metric);
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateLabels();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onReportSpeedChanged(boolean speed) {
|
||||
if (statsUtilities.isReportSpeed() == speed) {
|
||||
return false;
|
||||
}
|
||||
statsUtilities.setReportSpeed(speed);
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateLabels();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can
|
||||
* be accessed by multiple threads.
|
||||
*/
|
||||
private synchronized void resumeTrackDataHub() {
|
||||
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
|
||||
trackDataHub.registerTrackDataListener(this, EnumSet.of(
|
||||
ListenerDataType.SELECTED_TRACK_CHANGED,
|
||||
ListenerDataType.TRACK_UPDATES,
|
||||
ListenerDataType.LOCATION_UPDATES,
|
||||
ListenerDataType.DISPLAY_PREFERENCES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the trackDataHub. Needs to be synchronized because trackDataHub can
|
||||
* be accessed by multiple threads.
|
||||
*/
|
||||
private synchronized void pauseTrackDataHub() {
|
||||
trackDataHub.unregisterTrackDataListener(this);
|
||||
trackDataHub = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if recording. Needs to be synchronized because trackDataHub
|
||||
* can be accessed by multiple threads.
|
||||
*/
|
||||
private synchronized boolean isRecording() {
|
||||
return trackDataHub != null && trackDataHub.isRecordingSelected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the labels.
|
||||
*/
|
||||
private void updateLabels() {
|
||||
statsUtilities.updateUnits();
|
||||
statsUtilities.setSpeedLabel(R.id.speed_label, R.string.stat_speed, R.string.stat_pace);
|
||||
statsUtilities.setSpeedLabels();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current location.
|
||||
*
|
||||
* @param location the current location
|
||||
*/
|
||||
private void setLocation(Location location) {
|
||||
statsUtilities.setAltitude(R.id.elevation_register, location.getAltitude());
|
||||
statsUtilities.setLatLong(R.id.latitude_register, location.getLatitude());
|
||||
statsUtilities.setLatLong(R.id.longitude_register, location.getLongitude());
|
||||
statsUtilities.setSpeed(R.id.speed_register, location.getSpeed() * UnitConversions.MS_TO_KMH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current location to unknown.
|
||||
*/
|
||||
private void setLocationUnknown() {
|
||||
statsUtilities.setUnknown(R.id.elevation_register);
|
||||
statsUtilities.setUnknown(R.id.latitude_register);
|
||||
statsUtilities.setUnknown(R.id.longitude_register);
|
||||
statsUtilities.setUnknown(R.id.speed_register);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.View;
|
||||
import android.widget.TabHost;
|
||||
import android.widget.TabHost.TabContentFactory;
|
||||
import android.widget.TabHost.TabSpec;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* This is a helper class that implements a generic mechanism for associating
|
||||
* fragments with the tabs in a tab host. It relies on a trick. Normally a tab
|
||||
* host has a simple API for supplying a View or Intent that each tab will show.
|
||||
* This is not sufficient for switching between fragments. So instead we make
|
||||
* the content part of the tab host 0dp high (it is not shown) and the
|
||||
* TabManager supplies its own dummy view to show as the tab content. It listens
|
||||
* to changes in tabs, and takes care of switch to the correct fragment shown in
|
||||
* a separate content area whenever the selected tab changes.
|
||||
* <p>
|
||||
* Copied from the Fragment Tabs example in the API 4+ Support Demos.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class TabManager implements TabHost.OnTabChangeListener {
|
||||
|
||||
private final FragmentActivity fragmentActivity;
|
||||
private final TabHost tabHost;
|
||||
private final int containerId;
|
||||
private final HashMap<String, TabInfo> tabs = new HashMap<String, TabInfo>();
|
||||
private TabInfo lastTabInfo;
|
||||
|
||||
/**
|
||||
* An object to hold a tab's info.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
private static final class TabInfo {
|
||||
|
||||
private final String tag;
|
||||
private final Class<?> clss;
|
||||
private final Bundle bundle;
|
||||
private Fragment fragment;
|
||||
|
||||
public TabInfo(String tag, Class<?> clss, Bundle bundle) {
|
||||
this.tag = tag;
|
||||
this.clss = clss;
|
||||
this.bundle = bundle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy {@link TabContentFactory} that creates an empty view to satisfy the
|
||||
* {@link TabHost} API.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
private static class DummyTabContentFactory implements TabContentFactory {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public DummyTabContentFactory(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createTabContent(String tag) {
|
||||
View view = new View(context);
|
||||
view.setMinimumWidth(0);
|
||||
view.setMinimumHeight(0);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
public TabManager(FragmentActivity fragmentActivity, TabHost tabHost, int containerId) {
|
||||
this.fragmentActivity = fragmentActivity;
|
||||
this.tabHost = tabHost;
|
||||
this.containerId = containerId;
|
||||
tabHost.setOnTabChangedListener(this);
|
||||
}
|
||||
|
||||
public void addTab(TabSpec tabSpec, Class<?> clss, Bundle bundle) {
|
||||
tabSpec.setContent(new DummyTabContentFactory(fragmentActivity));
|
||||
|
||||
String tag = tabSpec.getTag();
|
||||
TabInfo tabInfo = new TabInfo(tag, clss, bundle);
|
||||
|
||||
/*
|
||||
* Check to see if we already have a fragment for this tab, probably from a
|
||||
* previously saved state. If so, deactivate it, because our initial state
|
||||
* is that a tab isn't shown.
|
||||
*/
|
||||
tabInfo.fragment = fragmentActivity.getSupportFragmentManager().findFragmentByTag(tag);
|
||||
if (tabInfo.fragment != null && !tabInfo.fragment.isDetached()) {
|
||||
FragmentTransaction fragmentTransaction = fragmentActivity.getSupportFragmentManager()
|
||||
.beginTransaction();
|
||||
fragmentTransaction.detach(tabInfo.fragment);
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
tabs.put(tag, tabInfo);
|
||||
tabHost.addTab(tabSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabChanged(String tabId) {
|
||||
TabInfo newTabInfo = tabs.get(tabId);
|
||||
if (lastTabInfo != newTabInfo) {
|
||||
FragmentTransaction fragmentTransaction = fragmentActivity.getSupportFragmentManager()
|
||||
.beginTransaction();
|
||||
if (lastTabInfo != null) {
|
||||
if (lastTabInfo.fragment != null) {
|
||||
fragmentTransaction.detach(lastTabInfo.fragment);
|
||||
}
|
||||
}
|
||||
if (newTabInfo != null) {
|
||||
if (newTabInfo.fragment == null) {
|
||||
newTabInfo.fragment = Fragment.instantiate(
|
||||
fragmentActivity, newTabInfo.clss.getName(), newTabInfo.bundle);
|
||||
fragmentTransaction.add(containerId, newTabInfo.fragment, newTabInfo.tag);
|
||||
} else {
|
||||
fragmentTransaction.attach(newTabInfo.fragment);
|
||||
}
|
||||
}
|
||||
|
||||
lastTabInfo = newTabInfo;
|
||||
fragmentTransaction.commit();
|
||||
fragmentActivity.getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,6 @@ import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.TabActivity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
@@ -44,21 +43,21 @@ import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TabHost;
|
||||
import android.widget.TabHost.TabSpec;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.List;
|
||||
@@ -69,21 +68,23 @@ import java.util.List;
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class TrackDetailActivity extends TabActivity implements OnTouchListener {
|
||||
public class TrackDetailActivity extends FragmentActivity {
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
public static final String EXTRA_WAYPOINT_ID = "waypoint_id";
|
||||
|
||||
private static final String TAG = TrackDetailActivity.class.getSimpleName();
|
||||
private static final String CURRENT_TAG_KEY = "tab";
|
||||
|
||||
private static final int DIALOG_INSTALL_EARTH_ID = 0;
|
||||
private static final int DIALOG_DELETE_CURRENT_ID = 1;
|
||||
|
||||
private static final int DIALOG_CHART_SETTINGS_ID = 2;
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
private TrackDataHub trackDataHub;
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private NavControls navControls;
|
||||
private TabHost tabHost;
|
||||
private TabManager tabManager;
|
||||
private long trackId;
|
||||
|
||||
private MenuItem stopRecordingMenuItem;
|
||||
@@ -95,12 +96,8 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
|
||||
private MenuItem editMenuItem;
|
||||
private MenuItem deleteMenuItem;
|
||||
|
||||
private final Runnable changeTab = new Runnable() {
|
||||
public void run() {
|
||||
getTabHost().setCurrentTab(navControls.getCurrentIcons());
|
||||
}
|
||||
};
|
||||
|
||||
private View mapViewContainer;
|
||||
|
||||
/*
|
||||
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
||||
* class. Anonymous inner class will get garbage collected.
|
||||
@@ -116,52 +113,68 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* We are not displaying driving directions. Just an arbitrary track that is
|
||||
* not associated to any licensed mapping data. Therefore it should be okay to
|
||||
* return false here and still comply with the terms of service.
|
||||
*/
|
||||
@Override
|
||||
protected boolean isRouteDisplayed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* We are displaying a location. This needs to return true in order to comply
|
||||
* with the terms of service.
|
||||
*/
|
||||
@Override
|
||||
protected boolean isLocationDisplayed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
ApiAdapterFactory.getApiAdapter().configureActionBarHomeAsUp(this);
|
||||
|
||||
setContentView(R.layout.track_detail);
|
||||
|
||||
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
trackDataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
||||
|
||||
final Resources res = getResources();
|
||||
final TabHost tabHost = getTabHost();
|
||||
tabHost.addTab(tabHost.newTabSpec(MAP_TAB_TAG)
|
||||
.setIndicator("Map", res.getDrawable(android.R.drawable.ic_menu_mapmode))
|
||||
.setContent(new Intent(this, MapActivity.class)));
|
||||
tabHost.addTab(tabHost.newTabSpec(STATS_TAB_TAG)
|
||||
.setIndicator("Stats", res.getDrawable(R.drawable.ic_menu_statistics))
|
||||
.setContent(new Intent(this, StatsActivity.class)));
|
||||
tabHost.addTab(tabHost.newTabSpec(CHART_TAB_TAG)
|
||||
.setIndicator("Chart", res.getDrawable(R.drawable.menu_elevation))
|
||||
.setContent(new Intent(this, ChartActivity.class)));
|
||||
mapViewContainer = getLayoutInflater().inflate(R.layout.mytracks_layout, null);
|
||||
|
||||
// Hide the tab widget itself. We'll use overlayed prev/next buttons to
|
||||
// switch between the tabs:
|
||||
tabHost.getTabWidget().setVisibility(View.GONE);
|
||||
tabHost = (TabHost) findViewById(android.R.id.tabhost);
|
||||
tabHost.setup();
|
||||
tabManager = new TabManager(this, tabHost, R.id.realtabcontent);
|
||||
TabSpec mapTabSpec = tabHost.newTabSpec(MAP_TAB_TAG).setIndicator(
|
||||
getString(R.string.track_detail_map_tab),
|
||||
getResources().getDrawable(android.R.drawable.ic_menu_mapmode));
|
||||
tabManager.addTab(mapTabSpec, MapFragment.class, null);
|
||||
TabSpec chartTabSpec = tabHost.newTabSpec(CHART_TAB_TAG).setIndicator(
|
||||
getString(R.string.track_detail_chart_tab),
|
||||
getResources().getDrawable(R.drawable.menu_elevation));
|
||||
tabManager.addTab(chartTabSpec, ChartFragment.class, null);
|
||||
TabSpec statsTabSpec = tabHost.newTabSpec(STATS_TAB_TAG).setIndicator(
|
||||
getString(R.string.track_detail_stats_tab),
|
||||
getResources().getDrawable(R.drawable.ic_menu_statistics));
|
||||
tabManager.addTab(statsTabSpec, StatsFragment.class, null);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAG_KEY));
|
||||
}
|
||||
|
||||
RelativeLayout layout = new RelativeLayout(this);
|
||||
LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
|
||||
layout.setLayoutParams(params);
|
||||
navControls = new NavControls(this, layout, getResources().obtainTypedArray(R.array.left_icons),
|
||||
getResources().obtainTypedArray(R.array.right_icons), changeTab);
|
||||
navControls.show();
|
||||
tabHost.addView(layout);
|
||||
layout.setOnTouchListener(this);
|
||||
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
public void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
@@ -174,6 +187,12 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
|
||||
trackRecordingServiceConnection.bindIfRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString(CURRENT_TAG_KEY, tabHost.getCurrentTabTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
@@ -211,11 +230,55 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
|
||||
startTrackListActivity();
|
||||
}
|
||||
});
|
||||
case DIALOG_CHART_SETTINGS_ID:
|
||||
return createChartSettingsDialog();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Dialog createChartSettingsDialog() {
|
||||
final ChartFragment chartFragment = (ChartFragment) getSupportFragmentManager()
|
||||
.findFragmentByTag(CHART_TAB_TAG);
|
||||
View view = getLayoutInflater().inflate(R.layout.chart_settings, null);
|
||||
final RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.chart_settings_x);
|
||||
radioGroup
|
||||
.check(chartFragment.getMode() == ChartView.Mode.BY_DISTANCE ? R.id.chart_settings_by_distance
|
||||
: R.id.chart_settings_by_time);
|
||||
|
||||
final CheckBox[] checkBoxes = new CheckBox[ChartView.NUM_SERIES];
|
||||
checkBoxes[ChartView.ELEVATION_SERIES] = (CheckBox) view
|
||||
.findViewById(R.id.chart_settings_elevation);
|
||||
checkBoxes[ChartView.SPEED_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_speed);
|
||||
checkBoxes[ChartView.POWER_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_power);
|
||||
checkBoxes[ChartView.CADENCE_SERIES] = (CheckBox) view
|
||||
.findViewById(R.id.chart_settings_cadence);
|
||||
checkBoxes[ChartView.HEART_RATE_SERIES] = (CheckBox) view
|
||||
.findViewById(R.id.chart_settings_heart_rate);
|
||||
|
||||
// set checkboxes values
|
||||
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
|
||||
checkBoxes[i].setChecked(chartFragment.isChartValueSeriesEnabled(i));
|
||||
}
|
||||
checkBoxes[ChartView.SPEED_SERIES].setText(chartFragment.isReportSpeed() ? R.string.stat_speed
|
||||
: R.string.stat_pace);
|
||||
|
||||
return new AlertDialog.Builder(this).setCancelable(true)
|
||||
.setNegativeButton(R.string.generic_cancel, null)
|
||||
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
chartFragment
|
||||
.setMode(radioGroup.getCheckedRadioButtonId() == R.id.chart_settings_by_distance ? ChartView.Mode.BY_DISTANCE
|
||||
: ChartView.Mode.BY_TIME);
|
||||
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
|
||||
chartFragment.setChartValueSeriesEnabled(i, checkBoxes[i].isChecked());
|
||||
}
|
||||
chartFragment.update();
|
||||
}
|
||||
}).setTitle(R.string.menu_chart_settings).setView(view).create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.track_detail, menu);
|
||||
@@ -253,13 +316,14 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
String currentTabTag = getTabHost().getCurrentTabTag();
|
||||
String currentTabTag = tabHost.getCurrentTabTag();
|
||||
menu.findItem(R.id.track_detail_chart_settings).setVisible(CHART_TAB_TAG.equals(currentTabTag));
|
||||
menu.findItem(R.id.track_detail_my_location).setVisible(MAP_TAB_TAG.equals(currentTabTag));
|
||||
|
||||
// Set map or satellite mode
|
||||
MapActivity mapActivity = getMapActivity();
|
||||
boolean isSatelliteMode = mapActivity != null ? mapActivity.isSatelliteView() : false;
|
||||
MapFragment mapFragment = (MapFragment) getSupportFragmentManager()
|
||||
.findFragmentByTag(MAP_TAB_TAG);
|
||||
boolean isSatelliteMode = mapFragment != null ? mapFragment.isSatelliteView() : false;
|
||||
menu.findItem(R.id.track_detail_satellite_mode).setVisible(MAP_TAB_TAG.equals(currentTabTag))
|
||||
.setTitle(isSatelliteMode ? R.string.menu_map_mode : R.string.menu_satellite_mode);
|
||||
|
||||
@@ -268,7 +332,7 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
MapActivity mapActivity;
|
||||
MapFragment mapFragment;
|
||||
Intent intent;
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
@@ -345,22 +409,19 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
|
||||
showDialog(DIALOG_DELETE_CURRENT_ID);
|
||||
return true;
|
||||
case R.id.track_detail_my_location:
|
||||
mapActivity = getMapActivity();
|
||||
if (mapActivity != null) {
|
||||
mapActivity.showMyLocation();
|
||||
mapFragment = (MapFragment) getSupportFragmentManager().findFragmentByTag(MAP_TAB_TAG);
|
||||
if (mapFragment != null) {
|
||||
mapFragment.showMyLocation();
|
||||
}
|
||||
return true;
|
||||
case R.id.track_detail_satellite_mode:
|
||||
mapActivity = getMapActivity();
|
||||
if (mapActivity != null) {
|
||||
mapActivity.setSatelliteView(!mapActivity.isSatelliteView());
|
||||
mapFragment = (MapFragment) getSupportFragmentManager().findFragmentByTag(MAP_TAB_TAG);
|
||||
if (mapFragment != null) {
|
||||
mapFragment.setSatelliteView(!mapFragment.isSatelliteView());
|
||||
}
|
||||
return true;
|
||||
case R.id.track_detail_chart_settings:
|
||||
ChartActivity chartActivity = getChartActivity();
|
||||
if (chartActivity != null) {
|
||||
chartActivity.showChartSettingsDialog();
|
||||
}
|
||||
showDialog(DIALOG_CHART_SETTINGS_ID);
|
||||
return true;
|
||||
case R.id.track_detail_sensor_state:
|
||||
startActivity(new Intent(this, SensorStateActivity.class));
|
||||
@@ -385,10 +446,11 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
|
||||
if (results != null) {
|
||||
long waypointId = results.getLongExtra(WaypointDetails.WAYPOINT_ID_EXTRA, -1L);
|
||||
if (waypointId != -1L) {
|
||||
MapActivity mapActivity = (MapActivity) getLocalActivityManager().getActivity(MAP_TAB_TAG);
|
||||
if (mapActivity != null) {
|
||||
getTabHost().setCurrentTab(0);
|
||||
mapActivity.showWaypoint(waypointId);
|
||||
MapFragment mapFragment = (MapFragment) getSupportFragmentManager()
|
||||
.findFragmentByTag(MAP_TAB_TAG);
|
||||
if (mapFragment != null) {
|
||||
tabHost.setCurrentTab(0);
|
||||
mapFragment.showWaypoint(waypointId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,12 +486,11 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
|
||||
return super.onTrackballEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
navControls.show();
|
||||
}
|
||||
return false;
|
||||
/**
|
||||
* @return the mapViewContainer
|
||||
*/
|
||||
public View getMapViewContainer() {
|
||||
return mapViewContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -447,15 +508,15 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
|
||||
// Get the waypointId
|
||||
long waypointId = intent.getLongExtra(EXTRA_WAYPOINT_ID, -1L);
|
||||
if (waypointId != -1L) {
|
||||
MapActivity mapActivity = getMapActivity();
|
||||
if (mapActivity != null) {
|
||||
getTabHost().setCurrentTab(0);
|
||||
mapActivity.showWaypoint(trackId, waypointId);
|
||||
MapFragment mapFragmet = (MapFragment) getSupportFragmentManager().findFragmentByTag(MAP_TAB_TAG);
|
||||
if (mapFragmet != null) {
|
||||
tabHost.setCurrentTab(0);
|
||||
mapFragmet.showWaypoint(trackId, waypointId);
|
||||
} else {
|
||||
Log.e(TAG, "MapActivity is null");
|
||||
Log.e(TAG, "MapFragment is null");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the menu.
|
||||
@@ -501,7 +562,6 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
|
||||
* Deletes the current track.
|
||||
*/
|
||||
private void deleteCurrentTrack() {
|
||||
long trackId = trackDataHub.getSelectedTrackId();
|
||||
MyTracksProviderUtils.Factory.get(TrackDetailActivity.this).deleteTrack(trackId);
|
||||
}
|
||||
|
||||
@@ -575,18 +635,4 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map activity, can be null.
|
||||
*/
|
||||
private MapActivity getMapActivity() {
|
||||
return (MapActivity) getLocalActivityManager().getActivity(MAP_TAB_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the chart activity, can be null.
|
||||
*/
|
||||
private ChartActivity getChartActivity() {
|
||||
return (ChartActivity) getLocalActivityManager().getActivity(CHART_TAB_TAG);
|
||||
}
|
||||
}
|
||||
@@ -13,152 +13,100 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||
import com.google.android.apps.mytracks.content.Sensor;
|
||||
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
import android.location.Location;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
import android.view.View;
|
||||
import android.widget.ZoomControls;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
/**
|
||||
* Tests {@link ChartActivity}.
|
||||
*
|
||||
* Tests {@link ChartFragment}.
|
||||
*
|
||||
* @author Youtao Liu
|
||||
*/
|
||||
public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartActivity> {
|
||||
public class ChartFragmentTest extends AndroidTestCase {
|
||||
|
||||
private Instrumentation instrumentation;
|
||||
private ChartActivity chartActivity;
|
||||
private View zoomIn;
|
||||
private View zoomOut;
|
||||
private int currentZoomLevel;
|
||||
private ChartFragment chartFragment;
|
||||
|
||||
// 10 is same with the default value in ChartView
|
||||
private final int MAX_ZOOM_LEVEL = 10;
|
||||
private final int MIN_ZOOM_LEVEL = 1;
|
||||
// The ratio from meter/second to kilometer/hour, the conversion is 60 * 60 /
|
||||
// 1000 = 3.6.
|
||||
private final double METER_PER_SECOND_TO_KILOMETER_PER_HOUR = 3.6;
|
||||
private final double KILOMETER_TO_METER = 1000.0;
|
||||
private final double HOURS_PER_UNIT = 60;
|
||||
|
||||
public ChartActivityTest() {
|
||||
super(ChartActivity.class);
|
||||
}
|
||||
private final double HOURS_PER_UNIT = 60.0;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
instrumentation = getInstrumentation();
|
||||
chartActivity = getActivity();
|
||||
chartFragment = new ChartFragment();
|
||||
chartFragment.setChartView(new ChartView(getContext()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests zooming in and zooming out.
|
||||
*/
|
||||
public void testZoomInAndZoomOut() {
|
||||
currentZoomLevel = chartActivity.getChartView().getZoomLevel();
|
||||
chartActivity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ZoomControls zoomControls = (ZoomControls) chartActivity.findViewById(R.id.elevation_zoom);
|
||||
zoomIn = zoomControls.getChildAt(0);
|
||||
zoomOut = zoomControls.getChildAt(1);
|
||||
// Invoke following two methods method to initial the display of
|
||||
// ZoomControls.
|
||||
zoomControls.setIsZoomInEnabled(chartActivity.getChartView().canZoomIn());
|
||||
zoomControls.setIsZoomOutEnabled(chartActivity.getChartView().canZoomOut());
|
||||
// Click zoomIn button to disable.
|
||||
for (int i = currentZoomLevel; i > MIN_ZOOM_LEVEL; i--) {
|
||||
zoomIn.performClick();
|
||||
}
|
||||
}
|
||||
});
|
||||
instrumentation.waitForIdleSync();
|
||||
assertEquals(false, zoomIn.isEnabled());
|
||||
assertEquals(true, zoomOut.isEnabled());
|
||||
chartActivity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Click to the second max zoom level
|
||||
for (int i = MIN_ZOOM_LEVEL; i < MAX_ZOOM_LEVEL - 1; i++) {
|
||||
zoomOut.performClick();
|
||||
assertEquals(true, zoomIn.isEnabled());
|
||||
assertEquals(true, zoomOut.isEnabled());
|
||||
}
|
||||
zoomOut.performClick();
|
||||
}
|
||||
});
|
||||
instrumentation.waitForIdleSync();
|
||||
assertEquals(true, zoomIn.isEnabled());
|
||||
assertEquals(false, zoomOut.isEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the incorrect values of sensor in {@link
|
||||
* ChartActivity#fillDataPoint(Location, double[])}.
|
||||
* Tests the logic to get the incorrect values of sensor in
|
||||
* {@link ChartFragment#fillDataPoint(Location, double[])}
|
||||
*/
|
||||
public void testFillDataPoint_sensorIncorrect() {
|
||||
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
|
||||
|
||||
// No input.
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation, false);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(Double.NaN, point[3]);
|
||||
assertEquals(Double.NaN, point[4]);
|
||||
assertEquals(Double.NaN, point[5]);
|
||||
|
||||
// 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);
|
||||
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);
|
||||
// Creates SensorDataSet.
|
||||
SensorDataSet sensorDataSet = myTracksLocation.getSensorDataSet();
|
||||
sensorDataSet = sensorDataSet.toBuilder().setPower(powerData).setCadence(cadenceData)
|
||||
.setHeartRate(heartRateData).build();
|
||||
sensorDataSet = sensorDataSet.toBuilder()
|
||||
.setPower(powerData)
|
||||
.setCadence(cadenceData)
|
||||
.setHeartRate(heartRateData)
|
||||
.build();
|
||||
myTracksLocation.setSensorData(sensorDataSet);
|
||||
// Test.
|
||||
point = fillDataPointTestHelper(myTracksLocation, false);
|
||||
point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(Double.NaN, point[3]);
|
||||
assertEquals(Double.NaN, point[4]);
|
||||
assertEquals(Double.NaN, point[5]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the correct values of sensor in {@link
|
||||
* ChartActivity#fillDataPoint(Location, double[])}.
|
||||
* Tests the logic to get the correct values of sensor in
|
||||
* {@link ChartFragment#fillDataPoint(Location, double[])}.
|
||||
*/
|
||||
public void testFillDataPoint_sensorCorrect() {
|
||||
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
|
||||
// No input.
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation, false);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(Double.NaN, point[3]);
|
||||
assertEquals(Double.NaN, point[4]);
|
||||
assertEquals(Double.NaN, point[5]);
|
||||
|
||||
// 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);
|
||||
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);
|
||||
|
||||
// Creates SensorDataSet.
|
||||
SensorDataSet sensorDataSet = myTracksLocation.getSensorDataSet();
|
||||
sensorDataSet = sensorDataSet.toBuilder().setPower(powerData).setCadence(cadenceData)
|
||||
.setHeartRate(heartRateData).build();
|
||||
sensorDataSet = sensorDataSet.toBuilder()
|
||||
.setPower(powerData)
|
||||
.setCadence(cadenceData)
|
||||
.setHeartRate(heartRateData)
|
||||
.build();
|
||||
myTracksLocation.setSensorData(sensorDataSet);
|
||||
// Test.
|
||||
point = fillDataPointTestHelper(myTracksLocation, false);
|
||||
point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(20.0, point[3]);
|
||||
assertEquals(20.0, point[4]);
|
||||
assertEquals(20.0, point[5]);
|
||||
@@ -166,61 +114,63 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartAct
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of metric Distance in
|
||||
* {@link ChartActivity#fillDataPoint(Location, double[])}.
|
||||
* {@link ChartFragment#fillDataPoint(Location, double[])}.
|
||||
*/
|
||||
public void testFillDataPoint_distanceMetric() {
|
||||
// By distance.
|
||||
chartActivity.getChartView().setMode(ChartView.Mode.BY_DISTANCE);
|
||||
chartFragment.getChartView().setMode(ChartView.Mode.BY_DISTANCE);
|
||||
// Resets last location and writes first location.
|
||||
MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation1, true);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation1);
|
||||
assertEquals(0.0, point[0]);
|
||||
|
||||
// The second is a same location, just different time.
|
||||
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
|
||||
point = fillDataPointTestHelper(myTracksLocation2, false);
|
||||
point = fillDataPointTestHelper(myTracksLocation2);
|
||||
assertEquals(0.0, point[0]);
|
||||
|
||||
// The third location is a new location, and use metric.
|
||||
MyTracksLocation myTracksLocation3 = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation3.setLatitude(23);
|
||||
point = fillDataPointTestHelper(myTracksLocation3, false);
|
||||
point = fillDataPointTestHelper(myTracksLocation3);
|
||||
|
||||
// Computes the distance between Latitude 22 and 23.
|
||||
float[] results = new float[4];
|
||||
Location.distanceBetween(myTracksLocation2.getLatitude(), myTracksLocation2.getLongitude(),
|
||||
myTracksLocation3.getLatitude(), myTracksLocation3.getLongitude(), results);
|
||||
double distance1 = results[0];
|
||||
assertEquals(distance1 / KILOMETER_TO_METER, point[0]);
|
||||
double distance1 = results[0] * UnitConversions.M_TO_KM;
|
||||
assertEquals(distance1, point[0]);
|
||||
|
||||
// The fourth location is a new location, and use metric.
|
||||
MyTracksLocation myTracksLocation4 = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation4.setLatitude(24);
|
||||
point = fillDataPointTestHelper(myTracksLocation4, false);
|
||||
point = fillDataPointTestHelper(myTracksLocation4);
|
||||
|
||||
// Computes the distance between Latitude 23 and 24.
|
||||
|
||||
Location.distanceBetween(myTracksLocation3.getLatitude(), myTracksLocation3.getLongitude(),
|
||||
myTracksLocation4.getLatitude(), myTracksLocation4.getLongitude(), results);
|
||||
double distance2 = results[0];
|
||||
assertEquals((distance1 + distance2) / KILOMETER_TO_METER, point[0]);
|
||||
double distance2 = results[0] * UnitConversions.M_TO_KM;
|
||||
assertEquals((distance1 + distance2), point[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of imperial Distance in
|
||||
* {@link ChartActivity#fillDataPoint(Location, double[])}.
|
||||
* {@link ChartFragment#fillDataPoint(Location, double[])}.
|
||||
*/
|
||||
public void testFillDataPoint_distanceImperial() {
|
||||
// Setups to use imperial.
|
||||
chartActivity.onUnitsChanged(false);
|
||||
chartFragment.setMetricUnits(false);
|
||||
|
||||
// The first is a same location, just different time.
|
||||
MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation1, true);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation1);
|
||||
assertEquals(0.0, point[0]);
|
||||
|
||||
// The second location is a new location, and use imperial.
|
||||
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation2.setLatitude(23);
|
||||
point = fillDataPointTestHelper(myTracksLocation2, false);
|
||||
point = fillDataPointTestHelper(myTracksLocation2);
|
||||
|
||||
/*
|
||||
* Computes the distance between Latitude 22 and 23. And for we set using
|
||||
* imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||
@@ -228,78 +178,84 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartAct
|
||||
float[] results = new float[4];
|
||||
Location.distanceBetween(myTracksLocation1.getLatitude(), myTracksLocation1.getLongitude(),
|
||||
myTracksLocation2.getLatitude(), myTracksLocation2.getLongitude(), results);
|
||||
double distance1 = results[0] * UnitConversions.KM_TO_MI;
|
||||
assertEquals(distance1 / KILOMETER_TO_METER, point[0]);
|
||||
double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
||||
assertEquals(distance1, point[0]);
|
||||
|
||||
// The third location is a new location, and use imperial.
|
||||
MyTracksLocation myTracksLocation3 = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation3.setLatitude(24);
|
||||
point = fillDataPointTestHelper(myTracksLocation3, false);
|
||||
point = fillDataPointTestHelper(myTracksLocation3);
|
||||
|
||||
/*
|
||||
* Computes the distance between Latitude 23 and 24. And for we set using
|
||||
* imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||
*/
|
||||
|
||||
Location.distanceBetween(myTracksLocation2.getLatitude(), myTracksLocation2.getLongitude(),
|
||||
myTracksLocation3.getLatitude(), myTracksLocation3.getLongitude(), results);
|
||||
double distance2 = results[0] * UnitConversions.KM_TO_MI;
|
||||
assertEquals(distance1 / KILOMETER_TO_METER + distance2 / KILOMETER_TO_METER , point[0]);
|
||||
double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
||||
assertEquals(distance1 + distance2, point[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the values of time in {@link ChartActivity#fillDataPoint(Location, double[])}.
|
||||
* Tests the logic to get the values of time in
|
||||
* {@link ChartFragment#fillDataPoint(Location, double[])}.
|
||||
*/
|
||||
public void testFillDataPoint_time() {
|
||||
// By time
|
||||
chartActivity.getChartView().setMode(ChartView.Mode.BY_TIME);
|
||||
chartFragment.getChartView().setMode(ChartView.Mode.BY_TIME);
|
||||
MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation1, true);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation1);
|
||||
assertEquals(0.0, point[0]);
|
||||
long timeSpan = 222;
|
||||
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation2.setTime(myTracksLocation1.getTime() + timeSpan);
|
||||
point = fillDataPointTestHelper(myTracksLocation2, false);
|
||||
point = fillDataPointTestHelper(myTracksLocation2);
|
||||
assertEquals((double) timeSpan, point[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of elevation in
|
||||
* {@link ChartActivity#fillDataPoint(Location, double[])} by one and two points.
|
||||
* {@link ChartFragment#fillDataPoint(Location, double[])} by one and two
|
||||
* points.
|
||||
*/
|
||||
public void testFillDataPoint_elevation() {
|
||||
MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
|
||||
|
||||
/*
|
||||
* At first, clear old points of elevation, so give true to the second
|
||||
* parameter. Then only one value INITIALLONGTITUDE in buffer.
|
||||
*/
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation1, true);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation1);
|
||||
assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[1]);
|
||||
|
||||
/*
|
||||
* Send another value to buffer, now there are two values, INITIALALTITUDE
|
||||
* and INITIALALTITUDE * 2.
|
||||
*/
|
||||
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
|
||||
point = fillDataPointTestHelper(myTracksLocation2, false);
|
||||
assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point[1]);
|
||||
point = fillDataPointTestHelper(myTracksLocation2);
|
||||
assertEquals(
|
||||
(TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of speed in
|
||||
* {@link ChartActivity#fillDataPoint(Location, double[])}. In this test, firstly remove all
|
||||
* points in memory, and then fill in two points one by one. The speed values
|
||||
* of these points are 129, 130.
|
||||
* {@link ChartFragment#fillDataPoint(Location, double[])}. In this test,
|
||||
* firstly remove all points in memory, and then fill in two points one by
|
||||
* one. The speed values of these points are 129, 130.
|
||||
*/
|
||||
public void testFillDataPoint_speed() {
|
||||
// Set max speed to make the speed of points are valid.
|
||||
chartActivity.setTrackMaxSpeed(200.0);
|
||||
chartFragment.setTrackMaxSpeed(200.0);
|
||||
|
||||
/*
|
||||
* At first, clear old points of speed, so give true to the second
|
||||
* parameter. It will not be filled in to the speed buffer.
|
||||
*/
|
||||
MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation1.setSpeed(129);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation1, true);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation1);
|
||||
assertEquals(0.0, point[2]);
|
||||
|
||||
/*
|
||||
@@ -307,12 +263,15 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartAct
|
||||
* location will be filled into speed buffer.
|
||||
*/
|
||||
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
|
||||
// Add a time span here to make sure the second point is valid, the value
|
||||
// 222 here is doesn't matter.
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point is valid, the value
|
||||
* 222 here is doesn't matter.
|
||||
*/
|
||||
myTracksLocation2.setTime(myTracksLocation1.getTime() + 222);
|
||||
myTracksLocation2.setSpeed(130);
|
||||
point = fillDataPointTestHelper(myTracksLocation2, false);
|
||||
assertEquals(130.0 * METER_PER_SECOND_TO_KILOMETER_PER_HOUR, point[2]);
|
||||
point = fillDataPointTestHelper(myTracksLocation2);
|
||||
assertEquals(130.0 * UnitConversions.MS_TO_KMH, point[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,12 +279,11 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartAct
|
||||
*/
|
||||
public void testFillDataPoint_speedImperial() {
|
||||
// Setups to use imperial.
|
||||
chartActivity.onUnitsChanged(false);
|
||||
chartFragment.setMetricUnits(false);
|
||||
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation.setSpeed(132);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation, true);
|
||||
assertEquals(132.0 * METER_PER_SECOND_TO_KILOMETER_PER_HOUR * UnitConversions.KM_TO_MI,
|
||||
point[2]);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(132.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -333,11 +291,11 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartAct
|
||||
*/
|
||||
public void testFillDataPoint_pace_nonZeroSpeed() {
|
||||
// Setups reportSpeed to false.
|
||||
chartActivity.onReportSpeedChanged(false);
|
||||
chartFragment.setReportSpeed(false);
|
||||
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation.setSpeed(134);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation, true);
|
||||
assertEquals(HOURS_PER_UNIT / (134.0 * METER_PER_SECOND_TO_KILOMETER_PER_HOUR), point[2]);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(HOURS_PER_UNIT / (134.0 * UnitConversions.MS_TO_KMH), point[2]);
|
||||
|
||||
}
|
||||
|
||||
@@ -347,26 +305,22 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartAct
|
||||
*/
|
||||
public void testFillDataPoint_pace_zeroSpeed() {
|
||||
// Setups reportSpeed to false.
|
||||
chartActivity.onReportSpeedChanged(false);
|
||||
chartFragment.setReportSpeed(false);
|
||||
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
|
||||
myTracksLocation.setSpeed(0);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation, true);
|
||||
assertEquals(Double.NaN, point[2]);
|
||||
double[] point = fillDataPointTestHelper(myTracksLocation);
|
||||
assertEquals(0.0, point[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to test fillDataPoint.
|
||||
*
|
||||
*
|
||||
* @param location location to fill
|
||||
* @param isNeedClear true to clear the track points
|
||||
* @return data of this location
|
||||
*/
|
||||
private double[] fillDataPointTestHelper(Location location, boolean isNeedClear) {
|
||||
if (isNeedClear) {
|
||||
chartActivity.clearTrackPoints();
|
||||
}
|
||||
private double[] fillDataPointTestHelper(Location location) {
|
||||
double[] point = new double[6];
|
||||
chartActivity.fillDataPoint(location, point);
|
||||
chartFragment.fillDataPoint(location, point);
|
||||
return point;
|
||||
}
|
||||
|
||||