merge heads

This commit is contained in:
Jimmy Shih
2012-07-16 16:38:46 -07:00
14 changed files with 910 additions and 754 deletions
@@ -0,0 +1,192 @@
/*
* Copyright 2011 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.content;
import static com.google.android.apps.mytracks.Constants.MAX_LOCATION_AGE_MS;
import com.google.android.apps.mytracks.Constants;
import com.google.android.maps.mytracks.R;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
/**
* Data source on the phone.
*
* @author Rodrigo Damazio
*/
public class DataSource {
private static final int NETWORK_PROVIDER_MIN_TIME = 5 * 60 * 1000; // 5 minutes
private static final String TAG = DataSource.class.getSimpleName();
private final Context context;
private final ContentResolver contentResolver;
private final LocationManager locationManager;
private final SensorManager sensorManager;
private final SharedPreferences sharedPreferences;
public DataSource(Context context) {
this.context = context;
contentResolver = context.getContentResolver();
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
}
/**
* Registers a content observer.
*
* @param uri the uri
* @param observer the observer
*/
public void registerContentObserver(Uri uri, ContentObserver observer) {
contentResolver.registerContentObserver(uri, false, observer);
}
/**
* Unregisters a content observer.
*
* @param observer the observer
*/
public void unregisterContentObserver(ContentObserver observer) {
contentResolver.unregisterContentObserver(observer);
}
/**
* Registers a location listener.
*
* @param listener the listener
*/
public void registerLocationListener(LocationListener listener) {
// Check if the GPS provider exists
if (locationManager.getProvider(LocationManager.GPS_PROVIDER) == null) {
listener.onProviderDisabled(LocationManager.GPS_PROVIDER);
unregisterLocationListener(listener);
return;
}
// Listen for GPS location
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
// Update the listener with the current provider state
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
listener.onProviderEnabled(LocationManager.GPS_PROVIDER);
} else {
listener.onProviderDisabled(LocationManager.GPS_PROVIDER);
}
// Listen for network location
try {
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, NETWORK_PROVIDER_MIN_TIME, 0, listener);
} catch (RuntimeException e) {
// Network location is optional, so just log the exception
Log.w(TAG, "Could not register for network location.", e);
}
}
/**
* Unregisters a location listener.
*
* @param listener the listener
*/
public void unregisterLocationListener(LocationListener listener) {
locationManager.removeUpdates(listener);
}
/**
* Gets the last known location.
*/
public Location getLastKnownLocation() {
Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (!isLocationRecent(location)) {
// Try network location
location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
int id = isLocationRecent(location)
? R.string.my_location_approximate_location : R.string.my_location_no_location;
Toast.makeText(context, id, Toast.LENGTH_LONG).show();
}
return location;
}
/**
* Returns true if the location is recent.
*
* @param location the location
*/
private boolean isLocationRecent(Location location) {
if (location == null) {
return false;
}
return location.getTime() > System.currentTimeMillis() - MAX_LOCATION_AGE_MS;
}
/**
* Registers a compass listener.
*
* @param listener the listener
*/
public void registerCompassListener(SensorEventListener listener) {
Sensor compass = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
if (compass == null) {
Log.d(TAG, "No compass sensor.");
return;
}
sensorManager.registerListener(listener, compass, SensorManager.SENSOR_DELAY_UI);
}
/**
* Unregisters a compass listener.
*
* @param listener the listener
*/
public void unregisterCompassListener(SensorEventListener listener) {
sensorManager.unregisterListener(listener);
}
/**
* Registers a shared preference change listener.
*
* @param listener the listener
*/
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
}
/**
* Unregisters a shared preference change listener.
*
* @param listener the listener
*/
public void unregisterOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener) {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
}
}
@@ -0,0 +1,78 @@
/*
* Copyright 2011 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.content;
import android.location.Location;
/**
* Listener to be invoked when {@link DataSource} changes.
*
* @author Jimmy Shih
*/
public interface DataSourceListener {
/**
* Notifies when the tracks table is updated.
*/
public void notifyTracksTableUpdated();
/**
* Notifies when the waypoints table is updated.
*/
public void notifyWaypointsTableUpdated();
/**
* Notifies when the track points table is updated.
*/
public void notifyTrackPointsTableUpdated();
/**
* Notifies when the current location changes.
*
* @param location the location
*/
public void notifyLocationChanged(Location location);
/**
* Notifies when the location provider enabled status changes.
*
* @param enabled true if enabled, false if not
*/
public void notifyLocationProviderEnabled(boolean enabled);
/**
* Notifies when the location provider availability status changes.
*
* @param available true if available, false if not
*/
public void notifyLocationProviderAvailable(boolean available);
/**
* Notifies when the compass heading changes.
*
* @param heading the new heading
*/
public void notifyHeadingChanged(float heading);
/**
* Notifies when a preference changes.
*
* @param key the preference key
*/
public void notifyPreferenceChanged(String key);
}
@@ -1,16 +1,29 @@
/*
* Copyright 2011 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.content; package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.Constants.TAG; import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver; import android.database.ContentObserver;
import android.hardware.Sensor; import android.hardware.Sensor;
import android.hardware.SensorEvent; import android.hardware.SensorEvent;
import android.hardware.SensorEventListener; import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location; import android.location.Location;
import android.location.LocationListener; import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
@@ -23,243 +36,281 @@ import java.util.EnumSet;
import java.util.Set; import java.util.Set;
/** /**
* External data source manager, which converts system-level events into My Tracks data events. * Data source manager. Creates observers/listeners and manages their
* * registration with {@link DataSource}. The observers/listeners calls
* {@link DataSourceListener} when data changes.
*
* @author Rodrigo Damazio * @author Rodrigo Damazio
*/ */
class DataSourceManager { public class DataSourceManager {
/** Single interface for receiving system events that were registered for. */ /**
interface DataSourceListener { * Observer when the tracks table is updated.
void notifyTrackUpdated(); *
void notifyWaypointUpdated(); * @author Jimmy Shih
void notifyPointsUpdated(); */
void notifyPreferenceChanged(String key); private class TracksTableObserver extends ContentObserver {
void notifyLocationProviderEnabled(boolean enabled);
void notifyLocationProviderAvailable(boolean available);
void notifyLocationChanged(Location loc);
void notifyHeadingChanged(float heading);
}
private final DataSourceListener listener; public TracksTableObserver() {
super(handler);
/** Observer for when the tracks table is updated. */
private class TrackObserver extends ContentObserver {
public TrackObserver() {
super(contentHandler);
} }
@Override @Override
public void onChange(boolean selfChange) { public void onChange(boolean selfChange) {
listener.notifyTrackUpdated(); dataSourceListener.notifyTracksTableUpdated();
} }
} }
/** Observer for when the waypoints table is updated. */ /**
private class WaypointObserver extends ContentObserver { * Observer when the waypoints table is updated.
public WaypointObserver() { *
super(contentHandler); * @author Jimmy Shih
*/
private class WaypointsTableObserver extends ContentObserver {
public WaypointsTableObserver() {
super(handler);
} }
@Override @Override
public void onChange(boolean selfChange) { public void onChange(boolean selfChange) {
listener.notifyWaypointUpdated(); dataSourceListener.notifyWaypointsTableUpdated();
} }
} }
/** Observer for when the points table is updated. */ /**
private class PointObserver extends ContentObserver { * Observer when the track points table is updated.
public PointObserver() { *
super(contentHandler); * @author Jimmy Shih
*/
private class TrackPointsTableObserver extends ContentObserver {
public TrackPointsTableObserver() {
super(handler);
} }
@Override @Override
public void onChange(boolean selfChange) { public void onChange(boolean selfChange) {
listener.notifyPointsUpdated(); dataSourceListener.notifyTrackPointsTableUpdated();
} }
} }
/** Listener for when preferences change. */ /**
private class HubSharedPreferenceListener implements OnSharedPreferenceChangeListener { * Listener for location changes.
@Override *
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { * @author Jimmy Shih
listener.notifyPreferenceChanged(key); */
} private class CurrentLocationListener implements LocationListener {
}
/** Listener for the current location (independent from track data). */
private class CurrentLocationListener implements
LocationListener {
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
if (!LocationManager.GPS_PROVIDER.equals(provider)) return;
listener.notifyLocationProviderAvailable(status == LocationProvider.AVAILABLE);
}
@Override @Override
public void onProviderEnabled(String provider) { public void onLocationChanged(Location location) {
if (!LocationManager.GPS_PROVIDER.equals(provider)) return; dataSourceListener.notifyLocationChanged(location);
listener.notifyLocationProviderEnabled(true);
} }
@Override @Override
public void onProviderDisabled(String provider) { public void onProviderDisabled(String provider) {
if (!LocationManager.GPS_PROVIDER.equals(provider)) return; if (!LocationManager.GPS_PROVIDER.equals(provider)) {
return;
listener.notifyLocationProviderEnabled(false); }
dataSourceListener.notifyLocationProviderEnabled(false);
} }
@Override @Override
public void onLocationChanged(Location location) { public void onProviderEnabled(String provider) {
listener.notifyLocationChanged(location); if (!LocationManager.GPS_PROVIDER.equals(provider)) {
return;
}
dataSourceListener.notifyLocationProviderEnabled(true);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
if (!LocationManager.GPS_PROVIDER.equals(provider)) {
return;
}
dataSourceListener.notifyLocationProviderAvailable(status == LocationProvider.AVAILABLE);
} }
} }
/** Listener for compass readings. */ /**
private class CompassListener implements * Listener for compass changes.
SensorEventListener { *
@Override * @author Jimmy Shih
public void onSensorChanged(SensorEvent event) { */
listener.notifyHeadingChanged(event.values[0]); private class CompassListener implements SensorEventListener {
}
@Override @Override
public void onAccuracyChanged(Sensor sensor, int accuracy) { public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do nothing // Do nothing
} }
@Override
public void onSensorChanged(SensorEvent event) {
dataSourceListener.notifyHeadingChanged(event.values[0]);
}
} }
/** Wrapper for registering internal listeners. */ /**
private final DataSourcesWrapper dataSources; * Listener for preference changes.
*
* @author Jimmy Shih
*/
private class PreferenceListener implements OnSharedPreferenceChangeListener {
// Internal listeners (to receive data from the system) @Override
private final Set<ListenerDataType> registeredListeners = public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
EnumSet.noneOf(ListenerDataType.class); dataSourceListener.notifyPreferenceChanged(key);
private final Handler contentHandler; }
private final ContentObserver pointObserver; }
private final ContentObserver waypointObserver;
private final ContentObserver trackObserver;
private final LocationListener locationListener;
private final OnSharedPreferenceChangeListener preferenceListener;
private final SensorEventListener compassListener;
DataSourceManager(DataSourceListener listener, DataSourcesWrapper dataSources) { private final DataSource dataSource;
this.listener = listener; private final DataSourceListener dataSourceListener;
this.dataSources = dataSources;
contentHandler = new Handler(); // Registered listeners
pointObserver = new PointObserver(); private final Set<TrackDataType> registeredListeners = EnumSet.noneOf(TrackDataType.class);
waypointObserver = new WaypointObserver();
trackObserver = new TrackObserver(); private final Handler handler;
private final TracksTableObserver tracksTableObserver;
private final WaypointsTableObserver waypointsTableObserver;
private final TrackPointsTableObserver trackPointsTableObserver;
private final CurrentLocationListener currentLocationListener;
private final CompassListener compassListener;
private final PreferenceListener preferenceListener;
public DataSourceManager(DataSource dataSource, DataSourceListener dataSourceListener) {
this.dataSource = dataSource;
this.dataSourceListener = dataSourceListener;
handler = new Handler();
tracksTableObserver = new TracksTableObserver();
waypointsTableObserver = new WaypointsTableObserver();
trackPointsTableObserver = new TrackPointsTableObserver();
currentLocationListener = new CurrentLocationListener();
compassListener = new CompassListener(); compassListener = new CompassListener();
locationListener = new CurrentLocationListener(); preferenceListener = new PreferenceListener();
preferenceListener = new HubSharedPreferenceListener();
} }
/** Updates the internal (sensor, position, etc) listeners. */ /**
void updateAllListeners(EnumSet<ListenerDataType> externallyNeededListeners) { * Updates listeners with data source.
EnumSet<ListenerDataType> neededListeners = EnumSet.copyOf(externallyNeededListeners); *
* @param listeners the listeners
*/
public void updateListeners(EnumSet<TrackDataType> listeners) {
EnumSet<TrackDataType> neededListeners = EnumSet.copyOf(listeners);
// Special case - map sampled-out points type to points type since they /*
// correspond to the same internal listener. * Map SAMPLED_OUT_POINT_UPDATES to POINT_UPDATES since they correspond to
if (neededListeners.contains(ListenerDataType.SAMPLED_OUT_POINT_UPDATES)) { * the same internal listener
neededListeners.remove(ListenerDataType.SAMPLED_OUT_POINT_UPDATES); */
neededListeners.add(ListenerDataType.POINT_UPDATES); if (neededListeners.contains(TrackDataType.SAMPLED_OUT_TRACK_POINTS)) {
neededListeners.remove(TrackDataType.SAMPLED_OUT_TRACK_POINTS);
neededListeners.add(TrackDataType.TRACK_POINTS_TABLE);
} }
Log.d(TAG, "Updating internal listeners to types " + neededListeners); Log.d(TAG, "Updating listeners " + neededListeners);
// Unnecessary = registered - needed // Unnecessary = registered - needed
Set<ListenerDataType> unnecessaryListeners = EnumSet.copyOf(registeredListeners); Set<TrackDataType> unnecessaryListeners = EnumSet.copyOf(registeredListeners);
unnecessaryListeners.removeAll(neededListeners); unnecessaryListeners.removeAll(neededListeners);
// Missing = needed - registered // Missing = needed - registered
Set<ListenerDataType> missingListeners = EnumSet.copyOf(neededListeners); Set<TrackDataType> missingListeners = EnumSet.copyOf(neededListeners);
missingListeners.removeAll(registeredListeners); missingListeners.removeAll(registeredListeners);
// Remove all unnecessary listeners. // Remove unnecessary listeners
for (ListenerDataType type : unnecessaryListeners) { for (TrackDataType trackDataType : unnecessaryListeners) {
unregisterListener(type); unregisterListener(trackDataType);
} }
// Add all missing listeners. // Add missing listeners
for (ListenerDataType type : missingListeners) { for (TrackDataType trackDataType : missingListeners) {
registerListener(type); registerListener(trackDataType);
} }
// Now all needed types are registered. // Update registered listeners
registeredListeners.clear(); registeredListeners.clear();
registeredListeners.addAll(neededListeners); registeredListeners.addAll(neededListeners);
} }
private void registerListener(ListenerDataType type) { /**
switch (type) { * Registers a listener with data source.
case COMPASS_UPDATES: { *
// Listen to compass * @param trackDataType the listener data type
Sensor compass = dataSources.getSensor(Sensor.TYPE_ORIENTATION); */
if (compass != null) { private void registerListener(TrackDataType trackDataType) {
Log.d(TAG, "TrackDataHub: Now registering sensor listener."); switch (trackDataType) {
dataSources.registerSensorListener(compassListener, compass, SensorManager.SENSOR_DELAY_UI); case SELECTED_TRACK:
} // Do nothing
break; break;
} case TRACKS_TABLE:
case LOCATION_UPDATES: dataSource.registerContentObserver(TracksColumns.CONTENT_URI, tracksTableObserver);
dataSources.requestLocationUpdates(locationListener);
break; break;
case POINT_UPDATES: case WAYPOINTS_TABLE:
dataSources.registerContentObserver( dataSource.registerContentObserver(WaypointsColumns.CONTENT_URI, waypointsTableObserver);
TrackPointsColumns.CONTENT_URI, false, pointObserver);
break; break;
case TRACK_UPDATES: case TRACK_POINTS_TABLE:
dataSources.registerContentObserver(TracksColumns.CONTENT_URI, false, trackObserver); dataSource.registerContentObserver(
TrackPointsColumns.CONTENT_URI, trackPointsTableObserver);
break; break;
case WAYPOINT_UPDATES: case SAMPLED_OUT_TRACK_POINTS:
dataSources.registerContentObserver( // Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES.
WaypointsColumns.CONTENT_URI, false, waypointObserver);
break; break;
case DISPLAY_PREFERENCES: case LOCATION:
dataSources.registerOnSharedPreferenceChangeListener(preferenceListener); dataSource.registerLocationListener(currentLocationListener);
break;
case COMPASS:
dataSource.registerCompassListener(compassListener);
break;
case PREFERENCE:
dataSource.registerOnSharedPreferenceChangeListener(preferenceListener);
break;
default:
break; break;
case SAMPLED_OUT_POINT_UPDATES:
throw new IllegalArgumentException("Should have been mapped to point updates");
} }
} }
private void unregisterListener(ListenerDataType type) { /**
switch (type) { * Unregisters a listener with data source.
case COMPASS_UPDATES: *
dataSources.unregisterSensorListener(compassListener); * @param trackDataType listener data type
*/
private void unregisterListener(TrackDataType trackDataType) {
switch (trackDataType) {
case SELECTED_TRACK:
// Do nothing
break; break;
case LOCATION_UPDATES: case TRACKS_TABLE:
dataSources.removeLocationUpdates(locationListener); dataSource.unregisterContentObserver(tracksTableObserver);
break; break;
case POINT_UPDATES: case WAYPOINTS_TABLE:
dataSources.unregisterContentObserver(pointObserver); dataSource.unregisterContentObserver(waypointsTableObserver);
break; break;
case TRACK_UPDATES: case TRACK_POINTS_TABLE:
dataSources.unregisterContentObserver(trackObserver); dataSource.unregisterContentObserver(trackPointsTableObserver);
break; break;
case WAYPOINT_UPDATES: case SAMPLED_OUT_TRACK_POINTS:
dataSources.unregisterContentObserver(waypointObserver); // Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES.
break; break;
case DISPLAY_PREFERENCES: case LOCATION:
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListener); dataSource.unregisterLocationListener(currentLocationListener);
break;
case COMPASS:
dataSource.unregisterCompassListener(compassListener);
break;
case PREFERENCE:
dataSource.unregisterOnSharedPreferenceChangeListener(preferenceListener);
break;
default:
break; break;
case SAMPLED_OUT_POINT_UPDATES:
throw new IllegalArgumentException("Should have been mapped to point updates");
} }
} }
/** Unregisters all internal (sensor, position, etc.) listeners. */ /**
void unregisterAllListeners() { * Unregisters all listeners with data source.
dataSources.removeLocationUpdates(locationListener); */
dataSources.unregisterSensorListener(compassListener); public void unregisterAllListeners() {
dataSources.unregisterContentObserver(trackObserver); for (TrackDataType trackDataType : TrackDataType.values()) {
dataSources.unregisterContentObserver(waypointObserver); unregisterListener(trackDataType);
dataSources.unregisterContentObserver(pointObserver); }
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListener);
} }
} }
@@ -1,54 +0,0 @@
/*
* Copyright 2011 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.content;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.location.Location;
import android.location.LocationListener;
import android.net.Uri;
/**
* Interface for abstracting registration of external data source listeners.
*
* @author Rodrigo Damazio
*/
interface DataSourcesWrapper {
// Preferences
void registerOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener);
void unregisterOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener);
// Content provider
void registerContentObserver(Uri contentUri, boolean descendents,
ContentObserver observer);
void unregisterContentObserver(ContentObserver observer);
// Sensors
Sensor getSensor(int type);
void registerSensorListener(SensorEventListener listener,
Sensor sensor, int sensorDelay);
void unregisterSensorListener(SensorEventListener listener);
// Location
boolean isLocationProviderEnabled(String provider);
void requestLocationUpdates(LocationListener listener);
void removeLocationUpdates(LocationListener listener);
Location getLastKnownLocation();
}
@@ -1,166 +0,0 @@
/*
* Copyright 2011 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.content;
import static com.google.android.apps.mytracks.Constants.MAX_LOCATION_AGE_MS;
import static com.google.android.apps.mytracks.Constants.MAX_NETWORK_AGE_MS;
import com.google.android.apps.mytracks.Constants;
import com.google.android.maps.mytracks.R;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
/**
* Real implementation of the data sources, which talks to system services.
*
* @author Rodrigo Damazio
*/
class DataSourcesWrapperImpl implements DataSourcesWrapper {
// System services
private final SensorManager sensorManager;
private final LocationManager locationManager;
private final ContentResolver contentResolver;
private final SharedPreferences sharedPreferences;
private final Context context;
DataSourcesWrapperImpl(Context context, SharedPreferences sharedPreferences) {
this.context = context;
this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
this.contentResolver = context.getContentResolver();
this.sharedPreferences = sharedPreferences;
}
@Override
public void registerOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener) {
sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
}
@Override
public void unregisterOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener) {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
}
@Override
public void registerContentObserver(Uri contentUri, boolean descendents,
ContentObserver observer) {
contentResolver.registerContentObserver(contentUri, descendents, observer);
}
@Override
public void unregisterContentObserver(ContentObserver observer) {
contentResolver.unregisterContentObserver(observer);
}
@Override
public Sensor getSensor(int type) {
return sensorManager.getDefaultSensor(type);
}
@Override
public void registerSensorListener(SensorEventListener listener,
Sensor sensor, int sensorDelay) {
sensorManager.registerListener(listener, sensor, sensorDelay);
}
@Override
public void unregisterSensorListener(SensorEventListener listener) {
sensorManager.unregisterListener(listener);
}
@Override
public boolean isLocationProviderEnabled(String provider) {
return locationManager.isProviderEnabled(provider);
}
@Override
public void requestLocationUpdates(LocationListener listener) {
// Check if the provider exists.
LocationProvider gpsProvider = locationManager.getProvider(LocationManager.GPS_PROVIDER);
if (gpsProvider == null) {
listener.onProviderDisabled(LocationManager.GPS_PROVIDER);
locationManager.removeUpdates(listener);
return;
}
// Listen to GPS location.
String providerName = gpsProvider.getName();
Log.d(Constants.TAG, "TrackDataHub: Using location provider " + providerName);
locationManager.requestLocationUpdates(providerName,
0 /*minTime*/, 0 /*minDist*/, listener);
// Give an initial update on provider state.
if (locationManager.isProviderEnabled(providerName)) {
listener.onProviderEnabled(providerName);
} else {
listener.onProviderDisabled(providerName);
}
// Listen to network location
try {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, listener);
} catch (RuntimeException e) {
// If anything at all goes wrong with getting a cell location do not
// abort. Cell location is not essential to this app.
Log.w(Constants.TAG, "Could not register network location listener.", e);
}
}
@Override
public Location getLastKnownLocation() {
// TODO: Let's look at more advanced algorithms to determine the best
// current location.
Location loc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
final long now = System.currentTimeMillis();
if (loc == null || loc.getTime() < now - MAX_LOCATION_AGE_MS) {
// We don't have a recent GPS fix, just use cell towers if available
loc = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
int toastResId = R.string.my_location_approximate_location;
if (loc == null || loc.getTime() < now - MAX_NETWORK_AGE_MS) {
// We don't have a recent cell tower location, let the user know:
toastResId = R.string.my_location_no_location;
}
// Let the user know we have only an approximate location:
Toast.makeText(context, toastResId, Toast.LENGTH_LONG).show();
}
return loc;
}
@Override
public void removeLocationUpdates(LocationListener listener) {
locationManager.removeUpdates(listener);
}
}
@@ -0,0 +1,105 @@
/*
* Copyright 2011 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.content;
import java.util.EnumSet;
/**
* State for a registered {@link TrackDataListener}.
*
* @author Jimmy Shih
*/
public class ListenerState {
private TrackDataListener trackDataListener;
private EnumSet<TrackDataType> trackDataTypes;
private long lastTrackId;
private long lastPointId;
private int lastSamplingFrequency;
private int numberOfLoadedPoints;
public ListenerState(
TrackDataListener trackDataListener, EnumSet<TrackDataType> trackDataTypes) {
this.trackDataListener = trackDataListener;
this.trackDataTypes = trackDataTypes;
}
/**
* Gets the {@link TrackDataListener}.
*/
public TrackDataListener getTrackDataListener() {
return trackDataListener;
}
/**
* Gets the track data types.
*/
public EnumSet<TrackDataType> getTrackDataTypes() {
return trackDataTypes;
}
/**
* Gets the last track id.
*/
public long getLastTrackId() {
return lastTrackId;
}
/**
* Gets the last point id.
*/
public long getLastPointId() {
return lastPointId;
}
/**
* Gets the last sampling frequency.
*/
public int getLastSamplingFrequency() {
return lastSamplingFrequency;
}
/**
* Get the number of loaded points.
*/
public int getNumberOfLoadedPoints() {
return numberOfLoadedPoints;
}
/**
* Sets the listener state.
*
* @param trackId track id
* @param pointId point id
* @param samplingFrequency sampling frequency
* @param loadedPoints number of loaded points
*/
public void setState(long trackId, long pointId, int samplingFrequency, int loadedPoints) {
lastTrackId = trackId;
lastPointId = pointId;
lastSamplingFrequency = samplingFrequency;
numberOfLoadedPoints = loadedPoints;
}
/**
* Resets state.
*/
public void resetState() {
setState(0L, 0L, 0, 0);
}
}
@@ -21,18 +21,14 @@ import static com.google.android.apps.mytracks.Constants.MAX_NETWORK_AGE_MS;
import static com.google.android.apps.mytracks.Constants.TAG; import static com.google.android.apps.mytracks.Constants.TAG;
import static com.google.android.apps.mytracks.Constants.TARGET_DISPLAYED_TRACK_POINTS; import static com.google.android.apps.mytracks.Constants.TARGET_DISPLAYED_TRACK_POINTS;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.DataSourceManager.DataSourceListener;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.DoubleBufferedLocationFactory; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.DoubleBufferedLocationFactory;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.TrackDataListener.ProviderState; import com.google.android.apps.mytracks.content.TrackDataListener.ProviderState;
import com.google.android.apps.mytracks.content.TrackDataListeners.ListenerRegistration;
import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R; import com.google.android.maps.mytracks.R;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor; import android.database.Cursor;
import android.hardware.GeomagneticField; import android.hardware.GeomagneticField;
import android.location.Location; import android.location.Location;
@@ -60,54 +56,23 @@ public class TrackDataHub {
// Overridable constants // Overridable constants
private final int targetNumPoints; private final int targetNumPoints;
/** Types of data that we can expose. */
public static enum ListenerDataType {
/** Listen to when the selected track changes. */
SELECTED_TRACK_CHANGED,
/** Listen to when the tracks change. */
TRACK_UPDATES,
/** Listen to when the waypoints change. */
WAYPOINT_UPDATES,
/** Listen to when the current track points change. */
POINT_UPDATES,
/**
* Listen to sampled-out points.
* Listening to this without listening to {@link #POINT_UPDATES}
* makes no sense and may yield unexpected results.
*/
SAMPLED_OUT_POINT_UPDATES,
/** Listen to updates to the current location. */
LOCATION_UPDATES,
/** Listen to updates to the current heading. */
COMPASS_UPDATES,
/** Listens to changes in display preferences. */
DISPLAY_PREFERENCES;
}
/** Listener which receives events from the system. */ /** Listener which receives events from the system. */
private class HubDataSourceListener implements DataSourceListener { private class HubDataSourceListener implements DataSourceListener {
@Override @Override
public void notifyTrackUpdated() { public void notifyTracksTableUpdated() {
TrackDataHub.this.notifyTrackUpdated(getListenersFor(ListenerDataType.TRACK_UPDATES)); TrackDataHub.this.notifyTrackUpdated(getListenersFor(TrackDataType.TRACKS_TABLE));
} }
@Override @Override
public void notifyWaypointUpdated() { public void notifyWaypointsTableUpdated() {
TrackDataHub.this.notifyWaypointUpdated(getListenersFor(ListenerDataType.WAYPOINT_UPDATES)); TrackDataHub.this.notifyWaypointUpdated(getListenersFor(TrackDataType.WAYPOINTS_TABLE));
} }
@Override @Override
public void notifyPointsUpdated() { public void notifyTrackPointsTableUpdated() {
TrackDataHub.this.notifyPointsUpdated(true, 0, 0, TrackDataHub.this.notifyPointsUpdated(true, 0, 0,
getListenersFor(ListenerDataType.POINT_UPDATES), getListenersFor(TrackDataType.TRACK_POINTS_TABLE),
getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES)); getListenersFor(TrackDataType.SAMPLED_OUT_TRACK_POINTS));
} }
@Override @Override
@@ -130,21 +95,20 @@ public class TrackDataHub {
@Override @Override
public void notifyLocationChanged(Location loc) { public void notifyLocationChanged(Location loc) {
TrackDataHub.this.notifyLocationChanged(loc, TrackDataHub.this.notifyLocationChanged(loc,
getListenersFor(ListenerDataType.LOCATION_UPDATES)); getListenersFor(TrackDataType.LOCATION));
} }
@Override @Override
public void notifyHeadingChanged(float heading) { public void notifyHeadingChanged(float heading) {
lastSeenMagneticHeading = heading; lastSeenMagneticHeading = heading;
maybeUpdateDeclination(); maybeUpdateDeclination();
TrackDataHub.this.notifyHeadingChanged(getListenersFor(ListenerDataType.COMPASS_UPDATES)); TrackDataHub.this.notifyHeadingChanged(getListenersFor(TrackDataType.COMPASS));
} }
} }
// Application services // Application services
private final Context context; private final Context context;
private final MyTracksProviderUtils providerUtils; private final MyTracksProviderUtils providerUtils;
private final SharedPreferences preferences;
// Get content notifications on the main thread, send listener callbacks in another. // Get content notifications on the main thread, send listener callbacks in another.
// This ensures listener calls are serialized. // This ensures listener calls are serialized.
@@ -152,12 +116,9 @@ public class TrackDataHub {
private Handler listenerHandler; private Handler listenerHandler;
/** Manager for external listeners (those from activities). */ /** Manager for external listeners (those from activities). */
private final TrackDataListeners dataListeners; private final TrackDataManager trackDataManager;
/** Wrapper for interacting with system data managers. */ private DataSource dataSource;
private DataSourcesWrapper dataSources;
/** Manager for system data listener registrations. */
private DataSourceManager dataSourceManager; private DataSourceManager dataSourceManager;
/** Condensed listener for system data listener events. */ /** Condensed listener for system data listener events. */
@@ -193,24 +154,19 @@ public class TrackDataHub {
* Builds a new {@link TrackDataHub} instance. * Builds a new {@link TrackDataHub} instance.
*/ */
public synchronized static TrackDataHub newInstance(Context context) { public synchronized static TrackDataHub newInstance(Context context) {
SharedPreferences preferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(context); MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(context);
return new TrackDataHub(context, return new TrackDataHub(
new TrackDataListeners(), context, new TrackDataManager(), providerUtils, TARGET_DISPLAYED_TRACK_POINTS);
preferences, providerUtils,
TARGET_DISPLAYED_TRACK_POINTS);
} }
/** /**
* Injection constructor. * Injection constructor.
*/ */
// @VisibleForTesting // @VisibleForTesting
TrackDataHub(Context ctx, TrackDataListeners listeners, SharedPreferences preferences, TrackDataHub(Context ctx, TrackDataManager trackDataManager, MyTracksProviderUtils providerUtils,
MyTracksProviderUtils providerUtils, int targetNumPoints) { int targetNumPoints) {
this.context = ctx; this.context = ctx;
this.dataListeners = listeners; this.trackDataManager = trackDataManager;
this.preferences = preferences;
this.providerUtils = providerUtils; this.providerUtils = providerUtils;
this.targetNumPoints = targetNumPoints; this.targetNumPoints = targetNumPoints;
this.locationFactory = new DoubleBufferedLocationFactory(); this.locationFactory = new DoubleBufferedLocationFactory();
@@ -233,12 +189,12 @@ public class TrackDataHub {
listenerHandlerThread = new HandlerThread("trackDataContentThread"); listenerHandlerThread = new HandlerThread("trackDataContentThread");
listenerHandlerThread.start(); listenerHandlerThread.start();
listenerHandler = new Handler(listenerHandlerThread.getLooper()); listenerHandler = new Handler(listenerHandlerThread.getLooper());
dataSources = newDataSources(); dataSource = newDataSource();
dataSourceManager = new DataSourceManager(dataSourceListener, dataSources); dataSourceManager = new DataSourceManager(dataSource, dataSourceListener);
// This may or may not register internal listeners, depending on whether // This may or may not register internal listeners, depending on whether
// we already had external listeners. // we already had external listeners.
dataSourceManager.updateAllListeners(getNeededListenerTypes()); dataSourceManager.updateListeners(getNeededListenerTypes());
loadSharedPreferences(); loadSharedPreferences();
// If there were listeners already registered, make sure they become up-to-date. // If there were listeners already registered, make sure they become up-to-date.
@@ -246,8 +202,8 @@ public class TrackDataHub {
} }
// @VisibleForTesting // @VisibleForTesting
protected DataSourcesWrapper newDataSources() { protected DataSource newDataSource() {
return new DataSourcesWrapperImpl(context, preferences); return new DataSource(context);
} }
/** /**
@@ -267,7 +223,7 @@ public class TrackDataHub {
started = false; started = false;
dataSources = null; dataSource = null;
dataSourceManager = null; dataSourceManager = null;
listenerHandlerThread = null; listenerHandlerThread = null;
listenerHandler = null; listenerHandler = null;
@@ -343,9 +299,9 @@ public class TrackDataHub {
} }
Log.i(TAG, "Forcing location update"); Log.i(TAG, "Forcing location update");
Location loc = dataSources.getLastKnownLocation(); Location loc = dataSource.getLastKnownLocation();
if (loc != null) { if (loc != null) {
notifyLocationChanged(loc, getListenersFor(ListenerDataType.LOCATION_UPDATES)); notifyLocationChanged(loc, getListenersFor(TrackDataType.LOCATION));
} }
} }
@@ -415,30 +371,31 @@ public class TrackDataHub {
} }
public void registerTrackDataListener( public void registerTrackDataListener(
TrackDataListener listener, EnumSet<ListenerDataType> dataTypes) { TrackDataListener listener, EnumSet<TrackDataType> dataTypes) {
synchronized (dataListeners) { synchronized (trackDataManager) {
ListenerRegistration registration = ListenerState listenerState = trackDataManager.registerListener(listener, dataTypes);
dataListeners.registerTrackDataListener(listener, dataTypes);
// Don't load any data or start internal listeners if start() hasn't been // Don't load any data or start internal listeners if start() hasn't been
// called. When it is called, we'll do both things. // called. When it is called, we'll do both things.
if (!isStarted()) return; if (!isStarted()) {
return;
}
loadNewDataForListener(registration); loadNewDataForListener(listenerState);
dataSourceManager.updateAllListeners(getNeededListenerTypes()); dataSourceManager.updateListeners(getNeededListenerTypes());
} }
} }
public void unregisterTrackDataListener(TrackDataListener listener) { public void unregisterTrackDataListener(TrackDataListener listener) {
synchronized (dataListeners) { synchronized (trackDataManager) {
dataListeners.unregisterTrackDataListener(listener); trackDataManager.unregisterListener(listener);
// Don't load any data or start internal listeners if start() hasn't been // Don't load any data or start internal listeners if start() hasn't been
// called. When it is called, we'll do both things. // called. When it is called, we'll do both things.
if (!isStarted()) return; if (!isStarted()) return;
dataSourceManager.updateAllListeners(getNeededListenerTypes()); dataSourceManager.updateListeners(getNeededListenerTypes());
} }
} }
@@ -446,33 +403,32 @@ public class TrackDataHub {
* Reloads all track data received so far into the specified listeners. * Reloads all track data received so far into the specified listeners.
*/ */
public void reloadDataForListener(TrackDataListener listener) { public void reloadDataForListener(TrackDataListener listener) {
ListenerRegistration registration; synchronized (trackDataManager) {
synchronized (dataListeners) { ListenerState listenerState = trackDataManager.getListenerState(listener);
registration = dataListeners.getRegistration(listener); listenerState.resetState();
registration.resetState(); loadNewDataForListener(listenerState);
loadNewDataForListener(registration);
} }
} }
/** /**
* Reloads all track data received so far into the specified listeners. * Reloads all track data received so far into the specified listeners.
* *
* Assumes it's called from a block that synchronizes on {@link #dataListeners}. * Assumes it's called from a block that synchronizes on {@link #trackDataManager}.
*/ */
private void loadNewDataForListener(final ListenerRegistration registration) { private void loadNewDataForListener(final ListenerState listenerState) {
if (!isStarted()) { if (!isStarted()) {
Log.w(TAG, "Not started, not reloading"); Log.w(TAG, "Not started, not reloading");
return; return;
} }
if (registration == null) { if (listenerState == null) {
Log.w(TAG, "Not reloading for null registration"); Log.w(TAG, "Not reloading for null listener state");
return; return;
} }
// If a listener happens to be added after this method but before the Runnable below is // If a listener happens to be added after this method but before the Runnable below is
// executed, it will have triggered a separate call to load data only up to the point this // executed, it will have triggered a separate call to load data only up to the point this
// listener got to. This is ensured by being synchronized on listeners. // listener got to. This is ensured by being synchronized on listeners.
final boolean isOnlyListener = (dataListeners.getNumListeners() == 1); final boolean isOnlyListener = (trackDataManager.getNumberOfListeners() == 1);
runInListenerThread(new Runnable() { runInListenerThread(new Runnable() {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -480,30 +436,31 @@ public class TrackDataHub {
public void run() { public void run() {
// Reload everything if either it's a different track, or the track has been resampled // Reload everything if either it's a different track, or the track has been resampled
// (this also covers the case of a new registration). // (this also covers the case of a new registration).
boolean reloadAll = registration.lastTrackId != selectedTrackId || boolean reloadAll = listenerState.getLastTrackId() != selectedTrackId ||
registration.lastSamplingFrequency != lastSamplingFrequency; listenerState.getLastSamplingFrequency() != lastSamplingFrequency;
Log.d(TAG, "Doing a " + (reloadAll ? "full" : "partial") + " reload for " + registration); Log.d(TAG, "Doing a " + (reloadAll ? "full" : "partial") + " reload for " + listenerState);
TrackDataListener listener = registration.listener; TrackDataListener listener = listenerState.getTrackDataListener();
Set<TrackDataListener> listenerSet = Collections.singleton(listener); Set<TrackDataListener> listenerSet = Collections.singleton(listener);
if (registration.isInterestedIn(ListenerDataType.DISPLAY_PREFERENCES)) { EnumSet<TrackDataType> trackDataTypes = listenerState.getTrackDataTypes();
if (trackDataTypes.contains(TrackDataType.PREFERENCE)) {
reloadAll |= listener.onUnitsChanged(metricUnits); reloadAll |= listener.onUnitsChanged(metricUnits);
reloadAll |= listener.onReportSpeedChanged(reportSpeed); reloadAll |= listener.onReportSpeedChanged(reportSpeed);
} }
if (reloadAll && registration.isInterestedIn(ListenerDataType.SELECTED_TRACK_CHANGED)) { if (reloadAll && trackDataTypes.contains(TrackDataType.SELECTED_TRACK)) {
notifySelectedTrackChanged(selectedTrackId, listenerSet); notifySelectedTrackChanged(selectedTrackId, listenerSet);
} }
if (registration.isInterestedIn(ListenerDataType.TRACK_UPDATES)) { if (trackDataTypes.contains(TrackDataType.TRACKS_TABLE)) {
notifyTrackUpdated(listenerSet); notifyTrackUpdated(listenerSet);
} }
boolean interestedInPoints = boolean interestedInPoints = trackDataTypes.contains(TrackDataType.TRACK_POINTS_TABLE);
registration.isInterestedIn(ListenerDataType.POINT_UPDATES); boolean interestedInSampledOutPoints = trackDataTypes.contains(
boolean interestedInSampledOutPoints = TrackDataType.SAMPLED_OUT_TRACK_POINTS);
registration.isInterestedIn(ListenerDataType.SAMPLED_OUT_POINT_UPDATES);
if (interestedInPoints || interestedInSampledOutPoints) { if (interestedInPoints || interestedInSampledOutPoints) {
long minPointId = 0; long minPointId = 0;
int previousNumPoints = 0; int previousNumPoints = 0;
@@ -513,8 +470,8 @@ public class TrackDataHub {
notifyPointsCleared(listenerSet); notifyPointsCleared(listenerSet);
} else { } else {
// Send only new points // Send only new points
minPointId = registration.lastPointId + 1; minPointId = listenerState.getLastPointId() + 1;
previousNumPoints = registration.numLoadedPoints; previousNumPoints = listenerState.getNumberOfLoadedPoints();
} }
// If this is the only listener we have registered, keep the state that we serve to it as // If this is the only listener we have registered, keep the state that we serve to it as
@@ -530,11 +487,11 @@ public class TrackDataHub {
interestedInSampledOutPoints ? listenerSet : Collections.EMPTY_SET); interestedInSampledOutPoints ? listenerSet : Collections.EMPTY_SET);
} }
if (registration.isInterestedIn(ListenerDataType.WAYPOINT_UPDATES)) { if (trackDataTypes.contains(TrackDataType.WAYPOINTS_TABLE)) {
notifyWaypointUpdated(listenerSet); notifyWaypointUpdated(listenerSet);
} }
if (registration.isInterestedIn(ListenerDataType.LOCATION_UPDATES)) { if (trackDataTypes.contains(TrackDataType.LOCATION)) {
if (lastSeenLocation != null) { if (lastSeenLocation != null) {
notifyLocationChanged(lastSeenLocation, true, listenerSet); notifyLocationChanged(lastSeenLocation, true, listenerSet);
} else { } else {
@@ -542,7 +499,7 @@ public class TrackDataHub {
} }
} }
if (registration.isInterestedIn(ListenerDataType.COMPASS_UPDATES)) { if (trackDataTypes.contains(TrackDataType.COMPASS)) {
notifyHeadingChanged(listenerSet); notifyHeadingChanged(listenerSet);
} }
} }
@@ -557,8 +514,8 @@ public class TrackDataHub {
Log.w(TAG, "Not started, not reloading"); Log.w(TAG, "Not started, not reloading");
return; return;
} }
synchronized (dataListeners) { synchronized (trackDataManager) {
if (!dataListeners.hasListeners()) { if (trackDataManager.getNumberOfListeners() == 0) {
Log.d(TAG, "No listeners, not reloading"); Log.d(TAG, "No listeners, not reloading");
return; return;
} }
@@ -569,33 +526,33 @@ public class TrackDataHub {
public void run() { public void run() {
// Ignore the return values here, we're already sending the full data set anyway // Ignore the return values here, we're already sending the full data set anyway
for (TrackDataListener listener : for (TrackDataListener listener :
getListenersFor(ListenerDataType.DISPLAY_PREFERENCES)) { getListenersFor(TrackDataType.PREFERENCE)) {
listener.onUnitsChanged(metricUnits); listener.onUnitsChanged(metricUnits);
listener.onReportSpeedChanged(reportSpeed); listener.onReportSpeedChanged(reportSpeed);
} }
notifySelectedTrackChanged(selectedTrackId, notifySelectedTrackChanged(selectedTrackId,
getListenersFor(ListenerDataType.SELECTED_TRACK_CHANGED)); getListenersFor(TrackDataType.SELECTED_TRACK));
notifyTrackUpdated(getListenersFor(ListenerDataType.TRACK_UPDATES)); notifyTrackUpdated(getListenersFor(TrackDataType.TRACKS_TABLE));
Set<TrackDataListener> pointListeners = Set<TrackDataListener> pointListeners =
getListenersFor(ListenerDataType.POINT_UPDATES); getListenersFor(TrackDataType.TRACK_POINTS_TABLE);
Set<TrackDataListener> sampledOutPointListeners = Set<TrackDataListener> sampledOutPointListeners =
getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES); getListenersFor(TrackDataType.SAMPLED_OUT_TRACK_POINTS);
notifyPointsCleared(pointListeners); notifyPointsCleared(pointListeners);
notifyPointsUpdated(true, 0, 0, pointListeners, sampledOutPointListeners); notifyPointsUpdated(true, 0, 0, pointListeners, sampledOutPointListeners);
notifyWaypointUpdated(getListenersFor(ListenerDataType.WAYPOINT_UPDATES)); notifyWaypointUpdated(getListenersFor(TrackDataType.WAYPOINTS_TABLE));
if (lastSeenLocation != null) { if (lastSeenLocation != null) {
notifyLocationChanged(lastSeenLocation, true, notifyLocationChanged(lastSeenLocation, true,
getListenersFor(ListenerDataType.LOCATION_UPDATES)); getListenersFor(TrackDataType.LOCATION));
} else { } else {
notifyFixType(); notifyFixType();
} }
notifyHeadingChanged(getListenersFor(ListenerDataType.COMPASS_UPDATES)); notifyHeadingChanged(getListenersFor(TrackDataType.COMPASS));
} }
}); });
} }
@@ -630,12 +587,12 @@ public class TrackDataHub {
@Override @Override
public void run() { public void run() {
Set<TrackDataListener> displayListeners = Set<TrackDataListener> displayListeners =
getListenersFor(ListenerDataType.DISPLAY_PREFERENCES); getListenersFor(TrackDataType.PREFERENCE);
for (TrackDataListener listener : displayListeners) { for (TrackDataListener listener : displayListeners) {
// TODO: Do the reloading just once for all interested listeners // TODO: Do the reloading just once for all interested listeners
if (listener.onReportSpeedChanged(reportSpeed)) { if (listener.onReportSpeedChanged(reportSpeed)) {
synchronized (dataListeners) { synchronized (trackDataManager) {
reloadDataForListener(listener); reloadDataForListener(listener);
} }
} }
@@ -651,11 +608,11 @@ public class TrackDataHub {
runInListenerThread(new Runnable() { runInListenerThread(new Runnable() {
@Override @Override
public void run() { public void run() {
Set<TrackDataListener> displayListeners = getListenersFor(ListenerDataType.DISPLAY_PREFERENCES); Set<TrackDataListener> displayListeners = getListenersFor(TrackDataType.PREFERENCE);
for (TrackDataListener listener : displayListeners) { for (TrackDataListener listener : displayListeners) {
if (listener.onUnitsChanged(metricUnits)) { if (listener.onUnitsChanged(metricUnits)) {
synchronized (dataListeners) { synchronized (trackDataManager) {
reloadDataForListener(listener); reloadDataForListener(listener);
} }
} }
@@ -683,7 +640,7 @@ public class TrackDataHub {
// Notify to everyone. // Notify to everyone.
Log.d(TAG, "Notifying fix type: " + state); Log.d(TAG, "Notifying fix type: " + state);
for (TrackDataListener listener : for (TrackDataListener listener :
getListenersFor(ListenerDataType.LOCATION_UPDATES)) { getListenersFor(TrackDataType.LOCATION)) {
listener.onProviderStateChange(state); listener.onProviderStateChange(state);
} }
} }
@@ -945,9 +902,9 @@ public class TrackDataHub {
Log.i(TAG, "Resampling point set after " + numLoadedPoints + " points."); Log.i(TAG, "Resampling point set after " + numLoadedPoints + " points.");
resetState(); resetState();
synchronized (dataListeners) { synchronized (trackDataManager) {
sampledListeners = getListenersFor(ListenerDataType.POINT_UPDATES); sampledListeners = getListenersFor(TrackDataType.TRACK_POINTS_TABLE);
sampledOutListeners = getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES); sampledOutListeners = getListenersFor(TrackDataType.SAMPLED_OUT_TRACK_POINTS);
} }
maxPointId = -1; maxPointId = -1;
minPointId = 0; minPointId = 0;
@@ -1030,12 +987,10 @@ public class TrackDataHub {
listener.onNewTrackPointsDone(); listener.onNewTrackPointsDone();
// Update the listener state // Update the listener state
ListenerRegistration registration = dataListeners.getRegistration(listener); ListenerState listenerState = trackDataManager.getListenerState(listener);
if (registration != null) { if (listenerState != null) {
registration.lastTrackId = currentSelectedTrackId; listenerState.setState(currentSelectedTrackId, localLastSeenLocationId,
registration.lastPointId = localLastSeenLocationId; pointSamplingFrequency, localNumLoadedPoints);
registration.lastSamplingFrequency = pointSamplingFrequency;
registration.numLoadedPoints = localNumLoadedPoints;
} }
} }
} }
@@ -1089,17 +1044,17 @@ public class TrackDataHub {
listenerHandler.post(runnable); listenerHandler.post(runnable);
} }
private Set<TrackDataListener> getListenersFor(ListenerDataType type) { private Set<TrackDataListener> getListenersFor(TrackDataType type) {
synchronized (dataListeners) { synchronized (trackDataManager) {
return dataListeners.getListenersFor(type); return trackDataManager.getListeners(type);
} }
} }
private EnumSet<ListenerDataType> getNeededListenerTypes() { private EnumSet<TrackDataType> getNeededListenerTypes() {
EnumSet<ListenerDataType> neededTypes = dataListeners.getAllRegisteredTypes(); EnumSet<TrackDataType> neededTypes = trackDataManager.getRegisteredTrackDataTypes();
// We always want preference updates. // We always want preference updates.
neededTypes.add(ListenerDataType.DISPLAY_PREFERENCES); neededTypes.add(TrackDataType.PREFERENCE);
return neededTypes; return neededTypes;
} }
@@ -1,159 +0,0 @@
/*
* Copyright 2011 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.content;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import android.util.Log;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* Manager for the external data listeners and their listening types.
*
* @author Rodrigo Damazio
*/
class TrackDataListeners {
/** Internal representation of a listener's registration. */
static class ListenerRegistration {
final TrackDataListener listener;
final EnumSet<ListenerDataType> types;
// State that was last notified to the listener, for resuming after a pause.
long lastTrackId;
long lastPointId;
int lastSamplingFrequency;
int numLoadedPoints;
public ListenerRegistration(TrackDataListener listener,
EnumSet<ListenerDataType> types) {
this.listener = listener;
this.types = types;
}
public boolean isInterestedIn(ListenerDataType type) {
return types.contains(type);
}
public void resetState() {
lastTrackId = 0L;
lastPointId = 0L;
lastSamplingFrequency = 0;
numLoadedPoints = 0;
}
@Override
public String toString() {
return "ListenerRegistration [listener=" + listener + ", types=" + types
+ ", lastTrackId=" + lastTrackId + ", lastPointId=" + lastPointId
+ ", lastSamplingFrequency=" + lastSamplingFrequency
+ ", numLoadedPoints=" + numLoadedPoints + "]";
}
}
/** Map of external listener to its registration details. */
private final Map<TrackDataListener, ListenerRegistration> registeredListeners =
new HashMap<TrackDataListener, ListenerRegistration>();
/** Map of data type to external listeners interested in it. */
private final Map<ListenerDataType, Set<TrackDataListener>> listenerSetsPerType =
new EnumMap<ListenerDataType, Set<TrackDataListener>>(ListenerDataType.class);
public TrackDataListeners() {
// Create sets for all data types at startup.
for (ListenerDataType type : ListenerDataType.values()) {
listenerSetsPerType.put(type, new LinkedHashSet<TrackDataListener>());
}
}
/**
* Registers a listener to send data to.
* It is ok to call this method before {@link TrackDataHub#start}, and in that case
* the data will only be passed to listeners when {@link TrackDataHub#start} is called.
*
* @param listener the listener to register
* @param dataTypes the type of data that the listener is interested in
*/
public ListenerRegistration registerTrackDataListener(final TrackDataListener listener, EnumSet<ListenerDataType> dataTypes) {
Log.d(TAG, "Registered track data listener: " + listener);
if (registeredListeners.containsKey(listener)) {
throw new IllegalStateException("Listener already registered");
}
ListenerRegistration registration = new ListenerRegistration(listener, dataTypes);
registeredListeners.put(listener, registration);
for (ListenerDataType type : dataTypes) {
// This is guaranteed not to be null.
Set<TrackDataListener> typeSet = listenerSetsPerType.get(type);
typeSet.add(listener);
}
return registration;
}
/**
* Unregisters a listener to send data to.
*
* @param listener the listener to unregister
*/
public void unregisterTrackDataListener(TrackDataListener listener) {
Log.d(TAG, "Unregistered track data listener: " + listener);
// Remove and keep the corresponding registration.
ListenerRegistration match = registeredListeners.remove(listener);
if (match == null) {
Log.w(TAG, "Tried to unregister listener which is not registered.");
return;
}
// Remove it from the per-type sets
for (ListenerDataType type : match.types) {
listenerSetsPerType.get(type).remove(listener);
}
}
public ListenerRegistration getRegistration(TrackDataListener listener) {
return registeredListeners.get(listener);
}
public Set<TrackDataListener> getListenersFor(ListenerDataType type) {
return listenerSetsPerType.get(type);
}
public EnumSet<ListenerDataType> getAllRegisteredTypes() {
EnumSet<ListenerDataType> listeners = EnumSet.noneOf(ListenerDataType.class);
for (ListenerRegistration registration : this.registeredListeners.values()) {
listeners.addAll(registration.types);
}
return listeners;
}
public boolean hasListeners() {
return !registeredListeners.isEmpty();
}
public int getNumListeners() {
return registeredListeners.size();
}
}
@@ -0,0 +1,127 @@
/*
* Copyright 2011 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.content;
import static com.google.android.apps.mytracks.Constants.TAG;
import android.util.Log;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* Manages register/unregister {@link TrackDataListener} and keeping the state
* for each registered listener.
*
* @author Rodrigo Damazio
*/
public class TrackDataManager {
// Map of listener to its state
private final Map<TrackDataListener, ListenerState>
listenerToStateMap = new HashMap<TrackDataListener, ListenerState>();
// Map of track data type to listeners
private final Map<TrackDataType, Set<TrackDataListener>>
typeToListenersMap = new EnumMap<TrackDataType, Set<TrackDataListener>>(TrackDataType.class);
public TrackDataManager() {
for (TrackDataType trackDataType : TrackDataType.values()) {
typeToListenersMap.put(trackDataType, new LinkedHashSet<TrackDataListener>());
}
}
/**
* Registers a listener.
*
* @param listener the listener
* @param trackDataTypes the track data types the listener is interested
*/
public ListenerState registerListener(
TrackDataListener listener, EnumSet<TrackDataType> trackDataTypes) {
if (listenerToStateMap.containsKey(listener)) {
throw new IllegalStateException("Listener is already registered");
}
ListenerState listenerState = new ListenerState(listener, trackDataTypes);
listenerToStateMap.put(listener, listenerState);
for (TrackDataType trackDataType : trackDataTypes) {
typeToListenersMap.get(trackDataType).add(listener);
}
return listenerState;
}
/**
* Unregisters a listener.
*
* @param listener the listener
*/
public void unregisterListener(TrackDataListener listener) {
ListenerState removed = listenerToStateMap.remove(listener);
if (removed == null) {
Log.w(TAG, "Tried to unregister a listener that is not registered.");
return;
}
// Remove the listener from the typeToListenersMap
for (TrackDataType trackDataType : removed.getTrackDataTypes()) {
typeToListenersMap.get(trackDataType).remove(listener);
}
}
/**
* Gets the number of {@link TrackDataListener}.
*/
public int getNumberOfListeners() {
return listenerToStateMap.size();
}
/**
* Gets the track listener state.
*
* @param listener the listener
*/
public ListenerState getListenerState(TrackDataListener listener) {
return listenerToStateMap.get(listener);
}
/**
* Gets the listeners for a {@link TrackDataType}.
*
* @param type the type
*/
public Set<TrackDataListener> getListeners(TrackDataType type) {
return typeToListenersMap.get(type);
}
/**
* Gets all the registered {@link TrackDataType}.
*/
public EnumSet<TrackDataType> getRegisteredTrackDataTypes() {
EnumSet<TrackDataType> types = EnumSet.noneOf(TrackDataType.class);
for (ListenerState registration : this.listenerToStateMap.values()) {
types.addAll(registration.getTrackDataTypes());
}
return types;
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2011 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.content;
/**
* Types of track data.
*
* @author Jimmy Shih
*/
public enum TrackDataType {
SELECTED_TRACK, // selected track changes
TRACKS_TABLE, // tracks table changes
WAYPOINTS_TABLE, // waypoints table changes
TRACK_POINTS_TABLE, // track points table changes
SAMPLED_OUT_TRACK_POINTS, // sampled-out track points changes
LOCATION, // location changes
COMPASS, // compass changes
PREFERENCE; // preference changes
}
@@ -19,12 +19,12 @@ package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.ChartView; import com.google.android.apps.mytracks.ChartView;
import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.MyTracksApplication; import com.google.android.apps.mytracks.MyTracksApplication;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.MyTracksLocation; import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor; import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet; import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub; 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.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.DoubleBuffer; import com.google.android.apps.mytracks.stats.DoubleBuffer;
@@ -360,12 +360,12 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private synchronized void resumeTrackDataHub() { private synchronized void resumeTrackDataHub() {
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub(); trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of( trackDataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED, TrackDataType.SELECTED_TRACK,
ListenerDataType.TRACK_UPDATES, TrackDataType.TRACKS_TABLE,
ListenerDataType.WAYPOINT_UPDATES, TrackDataType.WAYPOINTS_TABLE,
ListenerDataType.POINT_UPDATES, TrackDataType.TRACK_POINTS_TABLE,
ListenerDataType.SAMPLED_OUT_POINT_UPDATES, TrackDataType.SAMPLED_OUT_TRACK_POINTS,
ListenerDataType.DISPLAY_PREFERENCES)); TrackDataType.PREFERENCE));
} }
/** /**
@@ -19,11 +19,11 @@ package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.MapOverlay; import com.google.android.apps.mytracks.MapOverlay;
import com.google.android.apps.mytracks.MyTracksApplication; import com.google.android.apps.mytracks.MyTracksApplication;
import com.google.android.apps.mytracks.TrackDetailActivity; import com.google.android.apps.mytracks.TrackDetailActivity;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub; 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.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.apps.mytracks.stats.TripStatistics;
@@ -420,11 +420,11 @@ public class MapFragment extends Fragment
private synchronized void resumeTrackDataHub() { private synchronized void resumeTrackDataHub() {
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub(); trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of( trackDataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED, TrackDataType.SELECTED_TRACK,
ListenerDataType.WAYPOINT_UPDATES, TrackDataType.WAYPOINTS_TABLE,
ListenerDataType.POINT_UPDATES, TrackDataType.TRACK_POINTS_TABLE,
ListenerDataType.LOCATION_UPDATES, TrackDataType.LOCATION,
ListenerDataType.COMPASS_UPDATES)); TrackDataType.COMPASS));
} }
/** /**
@@ -17,9 +17,9 @@
package com.google.android.apps.mytracks.fragments; package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.MyTracksApplication; import com.google.android.apps.mytracks.MyTracksApplication;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub; 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.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.apps.mytracks.stats.TripStatistics;
@@ -257,10 +257,10 @@ public class StatsFragment extends Fragment implements TrackDataListener {
private synchronized void resumeTrackDataHub() { private synchronized void resumeTrackDataHub() {
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub(); trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of( trackDataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED, TrackDataType.SELECTED_TRACK,
ListenerDataType.TRACK_UPDATES, TrackDataType.TRACKS_TABLE,
ListenerDataType.LOCATION_UPDATES, TrackDataType.LOCATION,
ListenerDataType.DISPLAY_PREFERENCES)); TrackDataType.PREFERENCE));
} }
/** /**
@@ -15,23 +15,21 @@
*/ */
package com.google.android.apps.mytracks.content; package com.google.android.apps.mytracks.content;
import static com.google.android.testing.mocking.AndroidMock.anyInt;
import static com.google.android.testing.mocking.AndroidMock.capture; import static com.google.android.testing.mocking.AndroidMock.capture;
import static com.google.android.testing.mocking.AndroidMock.eq; import static com.google.android.testing.mocking.AndroidMock.eq;
import static com.google.android.testing.mocking.AndroidMock.expect; import static com.google.android.testing.mocking.AndroidMock.expect;
import static com.google.android.testing.mocking.AndroidMock.isA; import static com.google.android.testing.mocking.AndroidMock.isA;
import static com.google.android.testing.mocking.AndroidMock.leq; import static com.google.android.testing.mocking.AndroidMock.leq;
import static com.google.android.testing.mocking.AndroidMock.same;
import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationFactory; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationFactory;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener.ProviderState; import com.google.android.apps.mytracks.content.TrackDataListener.ProviderState;
import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext; import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext;
import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R; import com.google.android.maps.mytracks.R;
import com.google.android.testing.mocking.AndroidMock; import com.google.android.testing.mocking.AndroidMock;
import com.google.android.testing.mocking.UsesMocks;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@@ -69,8 +67,8 @@ public class TrackDataHubTest extends AndroidTestCase {
private MyTracksProviderUtils providerUtils; private MyTracksProviderUtils providerUtils;
private TrackDataHub hub; private TrackDataHub hub;
private TrackDataListeners listeners; private TrackDataManager trackDataManager;
private DataSourcesWrapper dataSources; private DataSource dataSource;
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
private TrackDataListener listener1; private TrackDataListener listener1;
private TrackDataListener listener2; private TrackDataListener listener2;
@@ -79,6 +77,7 @@ public class TrackDataHubTest extends AndroidTestCase {
private MockContext context; private MockContext context;
private float declination; private float declination;
@UsesMocks({MyTracksProviderUtils.class, DataSource.class, TrackDataListener.class})
@Override @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
super.setUp(); super.setUp();
@@ -90,13 +89,13 @@ public class TrackDataHubTest extends AndroidTestCase {
sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
providerUtils = AndroidMock.createMock("providerUtils", MyTracksProviderUtils.class); providerUtils = AndroidMock.createMock("providerUtils", MyTracksProviderUtils.class);
dataSources = AndroidMock.createNiceMock("dataSources", DataSourcesWrapper.class); dataSource = AndroidMock.createMock("dataSource", DataSource.class, context);
listeners = new TrackDataListeners(); trackDataManager = new TrackDataManager();
hub = new TrackDataHub(context, listeners, sharedPreferences, providerUtils, TARGET_POINTS) { hub = new TrackDataHub(context, trackDataManager, providerUtils, TARGET_POINTS) {
@Override @Override
protected DataSourcesWrapper newDataSources() { protected DataSource newDataSource() {
return dataSources; return dataSource;
} }
@Override @Override
@@ -119,18 +118,18 @@ public class TrackDataHubTest extends AndroidTestCase {
@Override @Override
protected void tearDown() throws Exception { protected void tearDown() throws Exception {
AndroidMock.reset(dataSources); AndroidMock.reset(dataSource);
// Expect everything to be unregistered. // Expect everything to be unregistered.
if (preferenceListenerCapture.hasCaptured()) { if (preferenceListenerCapture.hasCaptured()) {
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListenerCapture.getValue()); dataSource.unregisterOnSharedPreferenceChangeListener(preferenceListenerCapture.getValue());
} }
dataSources.removeLocationUpdates(isA(LocationListener.class)); dataSource.unregisterLocationListener(isA(LocationListener.class));
dataSources.unregisterSensorListener(isA(SensorEventListener.class)); dataSource.unregisterCompassListener(isA(SensorEventListener.class));
dataSources.unregisterContentObserver(isA(ContentObserver.class)); dataSource.unregisterContentObserver(isA(ContentObserver.class));
AndroidMock.expectLastCall().times(3); AndroidMock.expectLastCall().times(3);
AndroidMock.replay(dataSources); AndroidMock.replay(dataSource);
hub.stop(); hub.stop();
hub = null; hub = null;
@@ -143,8 +142,7 @@ public class TrackDataHubTest extends AndroidTestCase {
Track track = new Track(); Track track = new Track();
expect(providerUtils.getTrack(TRACK_ID)).andStubReturn(track); expect(providerUtils.getTrack(TRACK_ID)).andStubReturn(track);
expectStart(); expectStart();
dataSources.registerContentObserver( dataSource.registerContentObserver(eq(TracksColumns.CONTENT_URI), capture(observerCapture));
eq(TracksColumns.CONTENT_URI), eq(false), capture(observerCapture));
// Expect the initial loading. // Expect the initial loading.
// Both listeners (registered before and after start) should get the same data. // Both listeners (registered before and after start) should get the same data.
@@ -153,9 +151,9 @@ public class TrackDataHubTest extends AndroidTestCase {
replay(); replay();
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.TRACK_UPDATES)); hub.registerTrackDataListener(listener1, EnumSet.of(TrackDataType.TRACKS_TABLE));
hub.start(); hub.start();
hub.registerTrackDataListener(listener2, EnumSet.of(ListenerDataType.TRACK_UPDATES)); hub.registerTrackDataListener(listener2, EnumSet.of(TrackDataType.TRACKS_TABLE));
verifyAndReset(); verifyAndReset();
@@ -185,7 +183,7 @@ public class TrackDataHubTest extends AndroidTestCase {
verifyAndReset(); verifyAndReset();
// Unregister the other, expect internal unregistration // Unregister the other, expect internal unregistration
dataSources.unregisterContentObserver(observer); dataSource.unregisterContentObserver(observer);
replay(); replay();
@@ -321,8 +319,7 @@ public class TrackDataHubTest extends AndroidTestCase {
.andReturn(wpt2); .andReturn(wpt2);
expectStart(); expectStart();
dataSources.registerContentObserver( dataSource.registerContentObserver(eq(WaypointsColumns.CONTENT_URI), capture(observerCapture));
eq(WaypointsColumns.CONTENT_URI), eq(false), capture(observerCapture));
// Expect the initial loading. // Expect the initial loading.
// Both listeners (registered before and after start) should get the same data. // Both listeners (registered before and after start) should get the same data.
@@ -337,9 +334,9 @@ public class TrackDataHubTest extends AndroidTestCase {
replay(); replay();
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.WAYPOINT_UPDATES)); hub.registerTrackDataListener(listener1, EnumSet.of(TrackDataType.WAYPOINTS_TABLE));
hub.start(); hub.start();
hub.registerTrackDataListener(listener2, EnumSet.of(ListenerDataType.WAYPOINT_UPDATES)); hub.registerTrackDataListener(listener2, EnumSet.of(TrackDataType.WAYPOINTS_TABLE));
verifyAndReset(); verifyAndReset();
@@ -398,7 +395,7 @@ public class TrackDataHubTest extends AndroidTestCase {
verifyAndReset(); verifyAndReset();
// Unregister the other, expect internal unregistration // Unregister the other, expect internal unregistration
dataSources.unregisterContentObserver(observer); dataSource.unregisterContentObserver(observer);
replay(); replay();
@@ -412,8 +409,8 @@ public class TrackDataHubTest extends AndroidTestCase {
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>(); Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
expectStart(); expectStart();
dataSources.registerContentObserver( dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture));
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5); FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
expect(providerUtils.getLocationIterator( expect(providerUtils.getLocationIterator(
@@ -428,7 +425,7 @@ public class TrackDataHubTest extends AndroidTestCase {
replay(); replay();
hub.start(); hub.start();
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.POINT_UPDATES)); hub.registerTrackDataListener(listener1, EnumSet.of(TrackDataType.TRACK_POINTS_TABLE));
verifyAndReset(); verifyAndReset();
@@ -446,7 +443,7 @@ public class TrackDataHubTest extends AndroidTestCase {
replay(); replay();
hub.start(); hub.start();
hub.registerTrackDataListener(listener2, EnumSet.of(ListenerDataType.POINT_UPDATES)); hub.registerTrackDataListener(listener2, EnumSet.of(TrackDataType.TRACK_POINTS_TABLE));
verifyAndReset(); verifyAndReset();
@@ -497,8 +494,8 @@ public class TrackDataHubTest extends AndroidTestCase {
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>(); Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
expectStart(); expectStart();
dataSources.registerContentObserver( dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture));
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5); FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
expect(providerUtils.getLocationIterator( expect(providerUtils.getLocationIterator(
@@ -513,13 +510,13 @@ public class TrackDataHubTest extends AndroidTestCase {
replay(); replay();
hub.start(); hub.start();
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.POINT_UPDATES)); hub.registerTrackDataListener(listener1, EnumSet.of(TrackDataType.TRACK_POINTS_TABLE));
verifyAndReset(); verifyAndReset();
// Unregister // Unregister
ContentObserver observer = observerCapture.getValue(); ContentObserver observer = observerCapture.getValue();
dataSources.unregisterContentObserver(observer); dataSource.unregisterContentObserver(observer);
replay(); replay();
@@ -528,8 +525,8 @@ public class TrackDataHubTest extends AndroidTestCase {
verifyAndReset(); verifyAndReset();
// Register again, except only points since unregistered. // Register again, except only points since unregistered.
dataSources.registerContentObserver( dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture));
locationIterator = new FixedSizeLocationIterator(1, 10, 5); locationIterator = new FixedSizeLocationIterator(1, 10, 5);
expect(providerUtils.getLocationIterator( expect(providerUtils.getLocationIterator(
@@ -543,7 +540,7 @@ public class TrackDataHubTest extends AndroidTestCase {
replay(); replay();
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.POINT_UPDATES)); hub.registerTrackDataListener(listener1, EnumSet.of(TrackDataType.TRACK_POINTS_TABLE));
verifyAndReset(); verifyAndReset();
} }
@@ -552,8 +549,8 @@ public class TrackDataHubTest extends AndroidTestCase {
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>(); Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
expectStart(); expectStart();
dataSources.registerContentObserver( dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture));
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5); FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
expect(providerUtils.getLocationIterator( expect(providerUtils.getLocationIterator(
@@ -568,13 +565,13 @@ public class TrackDataHubTest extends AndroidTestCase {
replay(); replay();
hub.start(); hub.start();
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.POINT_UPDATES)); hub.registerTrackDataListener(listener1, EnumSet.of(TrackDataType.TRACK_POINTS_TABLE));
verifyAndReset(); verifyAndReset();
// Unregister // Unregister
ContentObserver observer = observerCapture.getValue(); ContentObserver observer = observerCapture.getValue();
dataSources.unregisterContentObserver(observer); dataSource.unregisterContentObserver(observer);
replay(); replay();
@@ -583,8 +580,8 @@ public class TrackDataHubTest extends AndroidTestCase {
verifyAndReset(); verifyAndReset();
// Register again after track changed, expect all points. // Register again after track changed, expect all points.
dataSources.registerContentObserver( dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture));
locationIterator = new FixedSizeLocationIterator(1, 10); locationIterator = new FixedSizeLocationIterator(1, 10);
expect(providerUtils.getLocationIterator( expect(providerUtils.getLocationIterator(
@@ -599,7 +596,7 @@ public class TrackDataHubTest extends AndroidTestCase {
replay(); replay();
hub.loadTrack(TRACK_ID + 1); hub.loadTrack(TRACK_ID + 1);
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.POINT_UPDATES)); hub.registerTrackDataListener(listener1, EnumSet.of(TrackDataType.TRACK_POINTS_TABLE));
verifyAndReset(); verifyAndReset();
} }
@@ -608,8 +605,8 @@ public class TrackDataHubTest extends AndroidTestCase {
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>(); Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
expectStart(); expectStart();
dataSources.registerContentObserver( dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture));
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 200, 4, 25, 71, 120); FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 200, 4, 25, 71, 120);
expect(providerUtils.getLocationIterator( expect(providerUtils.getLocationIterator(
@@ -627,9 +624,9 @@ public class TrackDataHubTest extends AndroidTestCase {
replay(); replay();
hub.registerTrackDataListener(listener1, hub.registerTrackDataListener(listener1,
EnumSet.of(ListenerDataType.POINT_UPDATES)); EnumSet.of(TrackDataType.TRACK_POINTS_TABLE));
hub.registerTrackDataListener(listener2, hub.registerTrackDataListener(listener2,
EnumSet.of(ListenerDataType.POINT_UPDATES, ListenerDataType.SAMPLED_OUT_POINT_UPDATES)); EnumSet.of(TrackDataType.TRACK_POINTS_TABLE, TrackDataType.SAMPLED_OUT_TRACK_POINTS));
hub.start(); hub.start();
verifyAndReset(); verifyAndReset();
@@ -639,8 +636,8 @@ public class TrackDataHubTest extends AndroidTestCase {
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>(); Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
expectStart(); expectStart();
dataSources.registerContentObserver( dataSource.registerContentObserver(
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture));
// Deliver 30 points (no sampling happens) // Deliver 30 points (no sampling happens)
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 30, 5); FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 30, 5);
@@ -656,7 +653,7 @@ public class TrackDataHubTest extends AndroidTestCase {
replay(); replay();
hub.start(); hub.start();
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.POINT_UPDATES)); hub.registerTrackDataListener(listener1, EnumSet.of(TrackDataType.TRACK_POINTS_TABLE));
verifyAndReset(); verifyAndReset();
@@ -702,16 +699,14 @@ public class TrackDataHubTest extends AndroidTestCase {
public void testCompassListen() throws Exception { public void testCompassListen() throws Exception {
AndroidMock.resetToDefault(listener1); AndroidMock.resetToDefault(listener1);
Sensor compass = newSensor(); expectStart();
expect(dataSources.getSensor(Sensor.TYPE_ORIENTATION)).andReturn(compass);
Capture<SensorEventListener> listenerCapture = new Capture<SensorEventListener>(); Capture<SensorEventListener> listenerCapture = new Capture<SensorEventListener>();
dataSources.registerSensorListener(capture(listenerCapture), same(compass), anyInt()); dataSource.registerCompassListener(capture(listenerCapture));
Capture<LocationListener> locationListenerCapture = new Capture<LocationListener>(); Capture<LocationListener> locationListenerCapture = new Capture<LocationListener>();
dataSources.requestLocationUpdates(capture(locationListenerCapture)); dataSource.registerLocationListener(capture(locationListenerCapture));
SensorEvent event = newSensorEvent(); SensorEvent event = newSensorEvent();
event.sensor = compass;
// First, get a dummy heading update. // First, get a dummy heading update.
listener1.onCurrentHeadingChanged(0.0); listener1.onCurrentHeadingChanged(0.0);
@@ -726,7 +721,7 @@ public class TrackDataHubTest extends AndroidTestCase {
replay(); replay();
hub.registerTrackDataListener(listener1, hub.registerTrackDataListener(listener1,
EnumSet.of(ListenerDataType.COMPASS_UPDATES, ListenerDataType.LOCATION_UPDATES)); EnumSet.of(TrackDataType.COMPASS, TrackDataType.LOCATION));
hub.start(); hub.start();
SensorEventListener sensorListener = listenerCapture.getValue(); SensorEventListener sensorListener = listenerCapture.getValue();
@@ -786,9 +781,7 @@ public class TrackDataHubTest extends AndroidTestCase {
PreferencesUtils.setBoolean(context, R.string.report_speed_key, true); PreferencesUtils.setBoolean(context, R.string.report_speed_key, true);
PreferencesUtils.setBoolean(context, R.string.metric_units_key, true); PreferencesUtils.setBoolean(context, R.string.metric_units_key, true);
Capture<OnSharedPreferenceChangeListener> listenerCapture = expectStart();
new Capture<OnSharedPreferenceChangeListener>();
dataSources.registerOnSharedPreferenceChangeListener(capture(listenerCapture));
expect(listener1.onUnitsChanged(true)).andReturn(false); expect(listener1.onUnitsChanged(true)).andReturn(false);
expect(listener2.onUnitsChanged(true)).andReturn(false); expect(listener2.onUnitsChanged(true)).andReturn(false);
@@ -797,9 +790,9 @@ public class TrackDataHubTest extends AndroidTestCase {
replay(); replay();
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.DISPLAY_PREFERENCES)); hub.registerTrackDataListener(listener1, EnumSet.of(TrackDataType.PREFERENCE));
hub.start(); hub.start();
hub.registerTrackDataListener(listener2, EnumSet.of(ListenerDataType.DISPLAY_PREFERENCES)); hub.registerTrackDataListener(listener2, EnumSet.of(TrackDataType.PREFERENCE));
verifyAndReset(); verifyAndReset();
@@ -809,12 +802,12 @@ public class TrackDataHubTest extends AndroidTestCase {
replay(); replay();
PreferencesUtils.setBoolean(context, R.string.report_speed_key, false); PreferencesUtils.setBoolean(context, R.string.report_speed_key, false);
OnSharedPreferenceChangeListener listener = listenerCapture.getValue(); OnSharedPreferenceChangeListener listener = preferenceListenerCapture.getValue();
listener.onSharedPreferenceChanged( listener.onSharedPreferenceChanged(
sharedPreferences, PreferencesUtils.getKey(context, R.string.report_speed_key)); sharedPreferences, PreferencesUtils.getKey(context, R.string.report_speed_key));
AndroidMock.verify(dataSources, providerUtils, listener1, listener2); AndroidMock.verify(dataSource, providerUtils, listener1, listener2);
AndroidMock.reset(dataSources, providerUtils, listener1, listener2); AndroidMock.reset(dataSource, providerUtils, listener1, listener2);
expect(listener1.onUnitsChanged(false)).andReturn(false); expect(listener1.onUnitsChanged(false)).andReturn(false);
expect(listener2.onUnitsChanged(false)).andReturn(false); expect(listener2.onUnitsChanged(false)).andReturn(false);
@@ -829,15 +822,15 @@ public class TrackDataHubTest extends AndroidTestCase {
} }
private void expectStart() { private void expectStart() {
dataSources.registerOnSharedPreferenceChangeListener(capture(preferenceListenerCapture)); dataSource.registerOnSharedPreferenceChangeListener(capture(preferenceListenerCapture));
} }
private void replay() { private void replay() {
AndroidMock.replay(dataSources, providerUtils, listener1, listener2); AndroidMock.replay(dataSource, providerUtils, listener1, listener2);
} }
private void verifyAndReset() { private void verifyAndReset() {
AndroidMock.verify(listener1, listener2, dataSources, providerUtils); AndroidMock.verify(listener1, listener2, dataSource, providerUtils);
AndroidMock.reset(listener1, listener2, dataSources, providerUtils); AndroidMock.reset(listener1, listener2, dataSource, providerUtils);
} }
} }