();
+
+ 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].
+ * data[0] = time/distance
+ * data[1] = elevation
+ * data[2] = speed
+ * data[3] = power
+ * data[4] = cadence
+ * data[5] = heart rate
+ *
+ * @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;
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java
deleted file mode 100644
index 1be7597b7..000000000
--- a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java
+++ /dev/null
@@ -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;
- }
-}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapFragment.java b/MyTracks/src/com/google/android/apps/mytracks/MapFragment.java
new file mode 100644
index 000000000..f8e5abf77
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/MapFragment.java
@@ -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);
+ }
+ }
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/NavControls.java b/MyTracks/src/com/google/android/apps/mytracks/NavControls.java
deleted file mode 100644
index 655a5fab8..000000000
--- a/MyTracks/src/com/google/android/apps/mytracks/NavControls.java
+++ /dev/null
@@ -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));
- }
-}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java
deleted file mode 100644
index ad7a046f6..000000000
--- a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java
+++ /dev/null
@@ -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.
- }
-}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsFragment.java b/MyTracks/src/com/google/android/apps/mytracks/StatsFragment.java
new file mode 100644
index 000000000..5cb3b3627
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/StatsFragment.java
@@ -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);
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/TabManager.java b/MyTracks/src/com/google/android/apps/mytracks/TabManager.java
new file mode 100644
index 000000000..27ef1fab4
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/TabManager.java
@@ -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.
+ *
+ * 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 tabs = new HashMap();
+ 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();
+ }
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java b/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java
index ed0483866..106c84ba3 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java
@@ -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);
- }
}
\ No newline at end of file
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/ChartActivityTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/ChartFragmentTest.java
similarity index 54%
rename from MyTracksTest/src/com/google/android/apps/mytracks/ChartActivityTest.java
rename to MyTracksTest/src/com/google/android/apps/mytracks/ChartFragmentTest.java
index 1ed95955a..69418ddd6 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/ChartActivityTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/ChartFragmentTest.java
@@ -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 {
+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