forked from upstream-mirrors/OpenTracks
Further splitting the data hub.
This commit is contained in:
@@ -0,0 +1,264 @@
|
|||||||
|
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.content.SharedPreferences;
|
||||||
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
|
import android.database.ContentObserver;
|
||||||
|
import android.hardware.Sensor;
|
||||||
|
import android.hardware.SensorEvent;
|
||||||
|
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.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External data source manager, which converts system-level events into My Tracks data events.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
class DataSourceManager {
|
||||||
|
|
||||||
|
/** Single interface for receiving system events that were registered for. */
|
||||||
|
interface DataSourceListener {
|
||||||
|
void notifyTrackUpdated();
|
||||||
|
void notifyWaypointUpdated();
|
||||||
|
void notifyPointsUpdated();
|
||||||
|
void notifyPreferenceChanged(String key);
|
||||||
|
void notifyLocationProviderEnabled(boolean enabled);
|
||||||
|
void notifyLocationChanged(Location loc);
|
||||||
|
void notifyHeadingChanged(float heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final DataSourceListener listener;
|
||||||
|
|
||||||
|
/** Observer for when the tracks table is updated. */
|
||||||
|
private class TrackObserver extends ContentObserver {
|
||||||
|
public TrackObserver() {
|
||||||
|
super(contentHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChange(boolean selfChange) {
|
||||||
|
listener.notifyTrackUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Observer for when the waypoints table is updated. */
|
||||||
|
private class WaypointObserver extends ContentObserver {
|
||||||
|
public WaypointObserver() {
|
||||||
|
super(contentHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChange(boolean selfChange) {
|
||||||
|
listener.notifyWaypointUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Observer for when the points table is updated. */
|
||||||
|
private class PointObserver extends ContentObserver {
|
||||||
|
public PointObserver() {
|
||||||
|
super(contentHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChange(boolean selfChange) {
|
||||||
|
listener.notifyPointsUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Listener for when preferences change. */
|
||||||
|
private class HubSharedPreferenceListener implements OnSharedPreferenceChangeListener {
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
|
listener.notifyPreferenceChanged(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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.notifyLocationProviderEnabled(status == LocationProvider.AVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProviderEnabled(String provider) {
|
||||||
|
if (!LocationManager.GPS_PROVIDER.equals(provider)) return;
|
||||||
|
|
||||||
|
listener.notifyLocationProviderEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProviderDisabled(String provider) {
|
||||||
|
if (!LocationManager.GPS_PROVIDER.equals(provider)) return;
|
||||||
|
|
||||||
|
listener.notifyLocationProviderEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocationChanged(Location location) {
|
||||||
|
listener.notifyLocationChanged(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Listener for compass readings. */
|
||||||
|
private class CompassListener implements
|
||||||
|
SensorEventListener {
|
||||||
|
@Override
|
||||||
|
public void onSensorChanged(SensorEvent event) {
|
||||||
|
listener.notifyHeadingChanged(event.values[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wrapper for registering internal listeners. */
|
||||||
|
private final DataSourcesWrapper dataSources;
|
||||||
|
|
||||||
|
// Internal listeners (to receive data from the system)
|
||||||
|
private final Set<ListenerDataType> registeredListeners =
|
||||||
|
EnumSet.noneOf(ListenerDataType.class);
|
||||||
|
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) {
|
||||||
|
this.listener = listener;
|
||||||
|
this.dataSources = dataSources;
|
||||||
|
|
||||||
|
contentHandler = new Handler();
|
||||||
|
pointObserver = new PointObserver();
|
||||||
|
waypointObserver = new WaypointObserver();
|
||||||
|
trackObserver = new TrackObserver();
|
||||||
|
|
||||||
|
compassListener = new CompassListener();
|
||||||
|
locationListener = new CurrentLocationListener();
|
||||||
|
preferenceListener = new HubSharedPreferenceListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the internal (sensor, position, etc) listeners. */
|
||||||
|
void updateAllListeners(EnumSet<ListenerDataType> externallyNeededListeners) {
|
||||||
|
EnumSet<ListenerDataType> neededListeners = EnumSet.copyOf(externallyNeededListeners);
|
||||||
|
|
||||||
|
// Special case - map sampled-out points type to points type since they
|
||||||
|
// correspond to the same internal listener.
|
||||||
|
if (neededListeners.contains(ListenerDataType.SAMPLED_OUT_POINT_UPDATES)) {
|
||||||
|
neededListeners.remove(ListenerDataType.SAMPLED_OUT_POINT_UPDATES);
|
||||||
|
neededListeners.add(ListenerDataType.POINT_UPDATES);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Updating internal listeners to types " + neededListeners);
|
||||||
|
|
||||||
|
// Unnecessary = registered - needed
|
||||||
|
Set<ListenerDataType> unnecessaryListeners = EnumSet.copyOf(registeredListeners);
|
||||||
|
unnecessaryListeners.removeAll(neededListeners);
|
||||||
|
|
||||||
|
// Missing = needed - registered
|
||||||
|
Set<ListenerDataType> missingListeners = EnumSet.copyOf(neededListeners);
|
||||||
|
missingListeners.removeAll(registeredListeners);
|
||||||
|
|
||||||
|
// Remove all unnecessary listeners.
|
||||||
|
for (ListenerDataType type : unnecessaryListeners) {
|
||||||
|
unregisterListener(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all missing listeners.
|
||||||
|
for (ListenerDataType type : missingListeners) {
|
||||||
|
registerListener(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now all needed types are registered.
|
||||||
|
registeredListeners.clear();
|
||||||
|
registeredListeners.addAll(neededListeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerListener(ListenerDataType type) {
|
||||||
|
switch (type) {
|
||||||
|
case COMPASS_UPDATES: {
|
||||||
|
// Listen to compass
|
||||||
|
Sensor compass = dataSources.getSensor(Sensor.TYPE_ORIENTATION);
|
||||||
|
if (compass != null) {
|
||||||
|
Log.d(TAG, "TrackDataHub: Now registering sensor listener.");
|
||||||
|
dataSources.registerSensorListener(compassListener, compass, SensorManager.SENSOR_DELAY_UI);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LOCATION_UPDATES:
|
||||||
|
dataSources.requestLocationUpdates(locationListener);
|
||||||
|
break;
|
||||||
|
case POINT_UPDATES:
|
||||||
|
dataSources.registerContentObserver(
|
||||||
|
TrackPointsColumns.CONTENT_URI, false, pointObserver);
|
||||||
|
break;
|
||||||
|
case TRACK_UPDATES:
|
||||||
|
dataSources.registerContentObserver(TracksColumns.CONTENT_URI, false, trackObserver);
|
||||||
|
break;
|
||||||
|
case WAYPOINT_UPDATES:
|
||||||
|
dataSources.registerContentObserver(
|
||||||
|
WaypointsColumns.CONTENT_URI, false, waypointObserver);
|
||||||
|
break;
|
||||||
|
case DISPLAY_PREFERENCES:
|
||||||
|
dataSources.registerOnSharedPreferenceChangeListener(preferenceListener);
|
||||||
|
break;
|
||||||
|
case SAMPLED_OUT_POINT_UPDATES:
|
||||||
|
throw new IllegalArgumentException("Should have been mapped to point updates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unregisterListener(ListenerDataType type) {
|
||||||
|
switch (type) {
|
||||||
|
case COMPASS_UPDATES:
|
||||||
|
dataSources.unregisterSensorListener(compassListener);
|
||||||
|
break;
|
||||||
|
case LOCATION_UPDATES:
|
||||||
|
dataSources.removeLocationUpdates(locationListener);
|
||||||
|
break;
|
||||||
|
case POINT_UPDATES:
|
||||||
|
dataSources.unregisterContentObserver(pointObserver);
|
||||||
|
break;
|
||||||
|
case TRACK_UPDATES:
|
||||||
|
dataSources.unregisterContentObserver(trackObserver);
|
||||||
|
break;
|
||||||
|
case WAYPOINT_UPDATES:
|
||||||
|
dataSources.unregisterContentObserver(waypointObserver);
|
||||||
|
break;
|
||||||
|
case DISPLAY_PREFERENCES:
|
||||||
|
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListener);
|
||||||
|
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() {
|
||||||
|
dataSources.removeLocationUpdates(locationListener);
|
||||||
|
dataSources.unregisterSensorListener(compassListener);
|
||||||
|
dataSources.unregisterContentObserver(trackObserver);
|
||||||
|
dataSources.unregisterContentObserver(waypointObserver);
|
||||||
|
dataSources.unregisterContentObserver(pointObserver);
|
||||||
|
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -24,11 +24,11 @@ import android.location.LocationListener;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for abstracting external data sources.
|
* Interface for abstracting registration of external data source listeners.
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
interface TrackDataSources {
|
interface DataSourcesWrapper {
|
||||||
// Preferences
|
// Preferences
|
||||||
void registerOnSharedPreferenceChangeListener(
|
void registerOnSharedPreferenceChangeListener(
|
||||||
OnSharedPreferenceChangeListener listener);
|
OnSharedPreferenceChangeListener listener);
|
||||||
+2
-2
@@ -42,7 +42,7 @@ import android.widget.Toast;
|
|||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
class TrackDataSourcesImpl implements TrackDataSources {
|
class DataSourcesWrapperImpl implements DataSourcesWrapper {
|
||||||
// System services
|
// System services
|
||||||
private final SensorManager sensorManager;
|
private final SensorManager sensorManager;
|
||||||
private final LocationManager locationManager;
|
private final LocationManager locationManager;
|
||||||
@@ -50,7 +50,7 @@ class TrackDataSourcesImpl implements TrackDataSources {
|
|||||||
private final SharedPreferences sharedPreferences;
|
private final SharedPreferences sharedPreferences;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
TrackDataSourcesImpl(Context context, SharedPreferences sharedPreferences) {
|
DataSourcesWrapperImpl(Context context, SharedPreferences sharedPreferences) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||||
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||||
@@ -19,44 +19,27 @@ import static com.google.android.apps.mytracks.Constants.MAX_LOCATION_AGE_MS;
|
|||||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.Constants;
|
import com.google.android.apps.mytracks.Constants;
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.DataSourceManager.DataSourceListener;
|
||||||
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.Track;
|
import com.google.android.apps.mytracks.content.TrackDataListeners.ListenerRegistration;
|
||||||
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
|
||||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||||
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.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
|
||||||
import android.database.ContentObserver;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.hardware.GeomagneticField;
|
import android.hardware.GeomagneticField;
|
||||||
import android.hardware.Sensor;
|
|
||||||
import android.hardware.SensorEvent;
|
|
||||||
import android.hardware.SensorEventListener;
|
|
||||||
import android.hardware.SensorManager;
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.location.LocationListener;
|
|
||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
import android.location.LocationProvider;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumMap;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,37 +89,72 @@ public class TrackDataHub {
|
|||||||
DISPLAY_PREFERENCES;
|
DISPLAY_PREFERENCES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Listener which receives events from the system. */
|
||||||
|
private class HubDataSourceListener implements DataSourceListener {
|
||||||
|
@Override
|
||||||
|
public void notifyTrackUpdated() {
|
||||||
|
TrackDataHub.this.notifyTrackUpdated(getListenersFor(ListenerDataType.TRACK_UPDATES));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyWaypointUpdated() {
|
||||||
|
TrackDataHub.this.notifyWaypointUpdated(getListenersFor(ListenerDataType.WAYPOINT_UPDATES));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyPointsUpdated() {
|
||||||
|
TrackDataHub.this.notifyPointsUpdated(true,
|
||||||
|
getListenersFor(ListenerDataType.POINT_UPDATES),
|
||||||
|
getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyPreferenceChanged(String key) {
|
||||||
|
TrackDataHub.this.notifyPreferenceChanged(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyLocationProviderEnabled(boolean enabled) {
|
||||||
|
hasProviderEnabled = enabled;
|
||||||
|
TrackDataHub.this.notifyFixType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyLocationChanged(Location loc) {
|
||||||
|
TrackDataHub.this.notifyLocationChanged(loc,
|
||||||
|
getListenersFor(ListenerDataType.LOCATION_UPDATES));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyHeadingChanged(float heading) {
|
||||||
|
lastSeenMagneticHeading = heading;
|
||||||
|
maybeUpdateDeclination();
|
||||||
|
TrackDataHub.this.notifyHeadingChanged(getListenersFor(ListenerDataType.COMPASS_UPDATES));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Application services
|
// Application services
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final TrackDataSources dataSources;
|
|
||||||
private final MyTracksProviderUtils providerUtils;
|
private final MyTracksProviderUtils providerUtils;
|
||||||
private final SharedPreferences preferences;
|
private final SharedPreferences preferences;
|
||||||
|
|
||||||
// Internal listeners (to receive data from the system)
|
|
||||||
private final ContentObserver pointObserver;
|
|
||||||
private final ContentObserver waypointObserver;
|
|
||||||
private final ContentObserver trackObserver;
|
|
||||||
private final LocationListener locationListener;
|
|
||||||
private final OnSharedPreferenceChangeListener preferenceListener;
|
|
||||||
private final SensorEventListener compassListener;
|
|
||||||
|
|
||||||
/** Set of internal listeners which are already registered. */
|
|
||||||
private final Set<ListenerDataType> registeredInternalListeners =
|
|
||||||
EnumSet.noneOf(ListenerDataType.class);
|
|
||||||
|
|
||||||
/** 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);
|
|
||||||
|
|
||||||
// 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.
|
||||||
private final HandlerThread listenerHandlerThread;
|
private final HandlerThread listenerHandlerThread;
|
||||||
private final Handler listenerHandler;
|
private final Handler listenerHandler;
|
||||||
|
|
||||||
|
/** Manager for external listeners (those from activities). */
|
||||||
|
private final TrackDataListeners listeners;
|
||||||
|
|
||||||
|
/** Wrapper for interacting with system data managers. */
|
||||||
|
private final DataSourcesWrapper dataSources;
|
||||||
|
|
||||||
|
/** Manager for system data listener registrations. */
|
||||||
|
private final DataSourceManager dataSourceManager;
|
||||||
|
|
||||||
|
/** Condensed listener for system data listener events. */
|
||||||
|
private final DataSourceListener dataSourceListener = new HubDataSourceListener();
|
||||||
|
|
||||||
/** Whether we've been started. */
|
/** Whether we've been started. */
|
||||||
private boolean started;
|
private boolean started;
|
||||||
|
|
||||||
@@ -163,158 +181,27 @@ public class TrackDataHub {
|
|||||||
private long lastSeenLocationId;
|
private long lastSeenLocationId;
|
||||||
private int numLoadedPoints;
|
private int numLoadedPoints;
|
||||||
|
|
||||||
/** Internal representation of a listener's registration. */
|
|
||||||
private static class ListenerRegistration {
|
|
||||||
final TrackDataListener listener;
|
|
||||||
final EnumSet<ListenerDataType> types;
|
|
||||||
// TODO: Add the last-notified point ID here, to allow pausing/resuming.
|
|
||||||
|
|
||||||
public ListenerRegistration(TrackDataListener listener,
|
|
||||||
EnumSet<ListenerDataType> types) {
|
|
||||||
this.listener = listener;
|
|
||||||
this.types = types;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInterestedIn(ListenerDataType type) {
|
|
||||||
return types.contains(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Callback for when the tracks table is updated. */
|
|
||||||
private class TrackObserverCallback implements Runnable {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
notifyTrackUpdated(getListenersFor(ListenerDataType.TRACK_UPDATES));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Callback for when the waypoints table is updated. */
|
|
||||||
private class WaypointObserverCallback implements Runnable {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
notifyWaypointUpdated(getListenersFor(ListenerDataType.WAYPOINT_UPDATES));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Callback for when the points table is updated. */
|
|
||||||
private class PointObserverCallback implements Runnable {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
notifyPointsUpdated(true,
|
|
||||||
getListenersFor(ListenerDataType.POINT_UPDATES),
|
|
||||||
getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Listener for when preferences change. */
|
|
||||||
private class HubSharedPreferenceListener implements OnSharedPreferenceChangeListener {
|
|
||||||
@Override
|
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
|
||||||
notifyPreferenceChanged(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic content observer which will call a given {@link Runnable} in the
|
|
||||||
* given handler if the content has changed and we're recording the selected
|
|
||||||
* track.
|
|
||||||
*/
|
|
||||||
private class TrackContentObserver extends ContentObserver {
|
|
||||||
private final Runnable callback;
|
|
||||||
|
|
||||||
public TrackContentObserver(Handler contentHandler, Runnable callback) {
|
|
||||||
super(contentHandler);
|
|
||||||
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChange(boolean selfChange) {
|
|
||||||
Log.v(TAG, "TrackContentObserver.onChange");
|
|
||||||
|
|
||||||
// We want to filter only updates from the selected track, but since
|
|
||||||
// we can't see what the update is, we'll let two cases pass:
|
|
||||||
// 1 - The point(s) was(ere) changed because it's a recording track
|
|
||||||
// (and thus we care about it if the recording is the selected one)
|
|
||||||
// 2 - The point(s) was(ere) changed because it's syncing a track
|
|
||||||
// (and thus there will be no new points for the selected one)
|
|
||||||
if (!isRecordingSelected()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update can potentially be lengthy, put it in its own thread:
|
|
||||||
runInListenerThread(callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 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;
|
|
||||||
|
|
||||||
hasProviderEnabled = (status == LocationProvider.AVAILABLE);
|
|
||||||
notifyFixType();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProviderEnabled(String provider) {
|
|
||||||
if (!LocationManager.GPS_PROVIDER.equals(provider)) return;
|
|
||||||
|
|
||||||
hasProviderEnabled = true;
|
|
||||||
notifyFixType();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProviderDisabled(String provider) {
|
|
||||||
if (!LocationManager.GPS_PROVIDER.equals(provider)) return;
|
|
||||||
|
|
||||||
hasProviderEnabled = false;
|
|
||||||
notifyFixType();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLocationChanged(Location location) {
|
|
||||||
notifyLocationChanged(location,
|
|
||||||
getListenersFor(ListenerDataType.LOCATION_UPDATES));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Listener for compass readings. */
|
|
||||||
private class CompassListener implements
|
|
||||||
SensorEventListener {
|
|
||||||
@Override
|
|
||||||
public void onSensorChanged(SensorEvent event) {
|
|
||||||
lastSeenMagneticHeading = event.values[0];
|
|
||||||
maybeUpdateDeclination();
|
|
||||||
notifyHeadingChanged(getListenersFor(ListenerDataType.COMPASS_UPDATES));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor.
|
* Default constructor.
|
||||||
*/
|
*/
|
||||||
public TrackDataHub(Context ctx, SharedPreferences preferences,
|
public TrackDataHub(Context ctx, SharedPreferences preferences,
|
||||||
MyTracksProviderUtils providerUtils) {
|
MyTracksProviderUtils providerUtils) {
|
||||||
this(ctx, new TrackDataSourcesImpl(ctx, preferences), preferences, providerUtils);
|
this(ctx, new DataSourcesWrapperImpl(ctx, preferences), new TrackDataListeners(),
|
||||||
|
preferences, providerUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection constructor.
|
* Injection constructor.
|
||||||
*/
|
*/
|
||||||
// @VisibleForTesting
|
// @VisibleForTesting
|
||||||
TrackDataHub(Context ctx, TrackDataSources dataSources, SharedPreferences preferences,
|
TrackDataHub(Context ctx, DataSourcesWrapper dataSources, TrackDataListeners listeners,
|
||||||
MyTracksProviderUtils providerUtils) {
|
SharedPreferences preferences, MyTracksProviderUtils providerUtils) {
|
||||||
this.context = ctx;
|
this.context = ctx;
|
||||||
this.dataSources = dataSources;
|
this.listeners = listeners;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
this.providerUtils = providerUtils;
|
this.providerUtils = providerUtils;
|
||||||
|
this.dataSources = dataSources;
|
||||||
|
this.dataSourceManager = new DataSourceManager(dataSourceListener, dataSources);
|
||||||
|
|
||||||
SELECTED_TRACK_KEY = context.getString(R.string.selected_track_key);
|
SELECTED_TRACK_KEY = context.getString(R.string.selected_track_key);
|
||||||
RECORDING_TRACK_KEY = context.getString(R.string.recording_track_key);
|
RECORDING_TRACK_KEY = context.getString(R.string.recording_track_key);
|
||||||
@@ -322,24 +209,9 @@ public class TrackDataHub {
|
|||||||
METRIC_UNITS_KEY = context.getString(R.string.metric_units_key);
|
METRIC_UNITS_KEY = context.getString(R.string.metric_units_key);
|
||||||
SPEED_REPORTING_KEY = context.getString(R.string.report_speed_key);
|
SPEED_REPORTING_KEY = context.getString(R.string.report_speed_key);
|
||||||
|
|
||||||
// Create sets for all data type at startup.
|
|
||||||
for (ListenerDataType type : ListenerDataType.values()) {
|
|
||||||
listenerSetsPerType.put(type, new LinkedHashSet<TrackDataListener>());
|
|
||||||
}
|
|
||||||
|
|
||||||
listenerHandlerThread = new HandlerThread("trackDataContentThread");
|
listenerHandlerThread = new HandlerThread("trackDataContentThread");
|
||||||
listenerHandlerThread.start();
|
listenerHandlerThread.start();
|
||||||
listenerHandler = new Handler(listenerHandlerThread.getLooper());
|
listenerHandler = new Handler(listenerHandlerThread.getLooper());
|
||||||
|
|
||||||
preferenceListener = new HubSharedPreferenceListener();
|
|
||||||
|
|
||||||
Handler contentHandler = new Handler();
|
|
||||||
pointObserver = new TrackContentObserver(contentHandler, new PointObserverCallback());
|
|
||||||
waypointObserver = new TrackContentObserver(contentHandler, new WaypointObserverCallback());
|
|
||||||
trackObserver = new TrackContentObserver(contentHandler, new TrackObserverCallback());
|
|
||||||
|
|
||||||
compassListener = new CompassListener();
|
|
||||||
locationListener = new CurrentLocationListener();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -354,12 +226,10 @@ public class TrackDataHub {
|
|||||||
}
|
}
|
||||||
started = true;
|
started = true;
|
||||||
|
|
||||||
dataSources.registerOnSharedPreferenceChangeListener(preferenceListener);
|
|
||||||
loadSharedPreferences();
|
|
||||||
|
|
||||||
// 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.
|
||||||
updateInternalListeners();
|
dataSourceManager.updateAllListeners(getNeededListenerTypes());
|
||||||
|
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.
|
||||||
// TODO: This should really only send new data (in a start-stop-start cycle).
|
// TODO: This should really only send new data (in a start-stop-start cycle).
|
||||||
@@ -386,10 +256,8 @@ public class TrackDataHub {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListener);
|
|
||||||
|
|
||||||
// Unregister internal listeners even if there are external listeners registered.
|
// Unregister internal listeners even if there are external listeners registered.
|
||||||
unregisterInternalListeners();
|
dataSourceManager.unregisterAllListeners();
|
||||||
|
|
||||||
started = false;
|
started = false;
|
||||||
}
|
}
|
||||||
@@ -515,163 +383,38 @@ public class TrackDataHub {
|
|||||||
loadTrack(-1);
|
loadTrack(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void registerTrackDataListener(
|
||||||
* Registers a listener to send data to.
|
TrackDataListener listener, EnumSet<ListenerDataType> dataTypes) {
|
||||||
* It is ok to call this method before {@link start}, and in that case
|
synchronized (listeners) {
|
||||||
* the data will only be passed to listeners when {@link start} is called.
|
ListenerRegistration registration = listeners.registerTrackDataListener(listener, dataTypes);
|
||||||
*
|
|
||||||
* @param listener the listener to register
|
|
||||||
* @param dataTypes the type of data that the listener is interested in
|
|
||||||
*/
|
|
||||||
public void registerTrackDataListener(final TrackDataListener listener, EnumSet<ListenerDataType> dataTypes) {
|
|
||||||
Log.d(TAG, "Registered track data listener: " + listener);
|
|
||||||
ListenerRegistration registration = new ListenerRegistration(listener, dataTypes);
|
|
||||||
synchronized (registeredListeners) {
|
|
||||||
if (registeredListeners.get(listener) != null) {
|
|
||||||
throw new IllegalStateException("Listener already registered");
|
|
||||||
}
|
|
||||||
registeredListeners.put(listener, registration);
|
|
||||||
|
|
||||||
for (ListenerDataType type : dataTypes) {
|
|
||||||
// This is guaranteed not to be null.
|
|
||||||
Set<TrackDataListener> typeSet = listenerSetsPerType.get(type);
|
|
||||||
typeSet.add(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 (!started) return;
|
if (!started) return;
|
||||||
|
|
||||||
reloadDataForListener(registration);
|
reloadDataForListener(registration);
|
||||||
}
|
|
||||||
|
|
||||||
updateInternalListeners();
|
dataSourceManager.updateAllListeners(getNeededListenerTypes());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters a listener to send data to.
|
|
||||||
*
|
|
||||||
* @param listener the listener to unregister
|
|
||||||
*/
|
|
||||||
public void unregisterTrackDataListener(TrackDataListener listener) {
|
public void unregisterTrackDataListener(TrackDataListener listener) {
|
||||||
Log.d(TAG, "Unregistered track data listener: " + listener);
|
synchronized (listeners) {
|
||||||
synchronized (registeredListeners) {
|
listeners.unregisterTrackDataListener(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 (!started) return;
|
if (!started) return;
|
||||||
|
|
||||||
|
dataSourceManager.updateAllListeners(getNeededListenerTypes());
|
||||||
}
|
}
|
||||||
updateInternalListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Updates the internal (sensor, position, etc) listeners. */
|
|
||||||
private void updateInternalListeners() {
|
|
||||||
synchronized (registeredListeners) {
|
|
||||||
Set<ListenerDataType> registeredListeners = registeredInternalListeners;
|
|
||||||
Set<ListenerDataType> neededListeners = EnumSet.noneOf(ListenerDataType.class);
|
|
||||||
for (ListenerRegistration registration : this.registeredListeners.values()) {
|
|
||||||
neededListeners.addAll(registration.types);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unnecessary = registered - needed
|
|
||||||
Set<ListenerDataType> unnecessaryListeners = EnumSet.copyOf(registeredListeners);
|
|
||||||
unnecessaryListeners.removeAll(neededListeners);
|
|
||||||
|
|
||||||
// Missing = needed - registered
|
|
||||||
Set<ListenerDataType> missingListeners = EnumSet.copyOf(neededListeners);
|
|
||||||
missingListeners.removeAll(registeredListeners);
|
|
||||||
|
|
||||||
// Remove all unnecessary listeners.
|
|
||||||
for (ListenerDataType type : unnecessaryListeners) {
|
|
||||||
switch (type) {
|
|
||||||
case COMPASS_UPDATES:
|
|
||||||
dataSources.unregisterSensorListener(compassListener);
|
|
||||||
break;
|
|
||||||
case LOCATION_UPDATES:
|
|
||||||
dataSources.removeLocationUpdates(locationListener);
|
|
||||||
break;
|
|
||||||
case POINT_UPDATES:
|
|
||||||
case SAMPLED_OUT_POINT_UPDATES:
|
|
||||||
// Special case - don't unregister if the other type is needed.
|
|
||||||
if (!neededListeners.contains(ListenerDataType.POINT_UPDATES) &&
|
|
||||||
!neededListeners.contains(ListenerDataType.SAMPLED_OUT_POINT_UPDATES)) {
|
|
||||||
dataSources.unregisterContentObserver(pointObserver);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TRACK_UPDATES:
|
|
||||||
dataSources.unregisterContentObserver(trackObserver);
|
|
||||||
break;
|
|
||||||
case WAYPOINT_UPDATES:
|
|
||||||
dataSources.unregisterContentObserver(waypointObserver);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all missing listeners.
|
|
||||||
for (ListenerDataType type : missingListeners) {
|
|
||||||
switch (type) {
|
|
||||||
case COMPASS_UPDATES: {
|
|
||||||
// Listen to compass
|
|
||||||
Sensor compass = dataSources.getSensor(Sensor.TYPE_ORIENTATION);
|
|
||||||
if (compass != null) {
|
|
||||||
Log.d(Constants.TAG, "TrackDataHub: Now registering sensor listener.");
|
|
||||||
dataSources.registerSensorListener(compassListener, compass, SensorManager.SENSOR_DELAY_UI);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case LOCATION_UPDATES:
|
|
||||||
dataSources.requestLocationUpdates(locationListener);
|
|
||||||
break;
|
|
||||||
case POINT_UPDATES:
|
|
||||||
case SAMPLED_OUT_POINT_UPDATES:
|
|
||||||
// Special case - don't register if the other type was already registered.
|
|
||||||
if (!registeredListeners.contains(ListenerDataType.POINT_UPDATES) &&
|
|
||||||
!registeredListeners.contains(ListenerDataType.SAMPLED_OUT_POINT_UPDATES)) {
|
|
||||||
dataSources.registerContentObserver(
|
|
||||||
TrackPointsColumns.CONTENT_URI, false, pointObserver);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TRACK_UPDATES:
|
|
||||||
dataSources.registerContentObserver(TracksColumns.CONTENT_URI, false, trackObserver);
|
|
||||||
break;
|
|
||||||
case WAYPOINT_UPDATES:
|
|
||||||
dataSources.registerContentObserver(
|
|
||||||
WaypointsColumns.CONTENT_URI, false, waypointObserver);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now all needed types are registered.
|
|
||||||
registeredInternalListeners.clear();
|
|
||||||
registeredInternalListeners.addAll(neededListeners);
|
|
||||||
} // synchronized
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Unregisters all internal (sensor, position, etc.) listeners. */
|
|
||||||
private void unregisterInternalListeners() {
|
|
||||||
dataSources.removeLocationUpdates(locationListener);
|
|
||||||
dataSources.unregisterSensorListener(compassListener);
|
|
||||||
dataSources.unregisterContentObserver(trackObserver);
|
|
||||||
dataSources.unregisterContentObserver(waypointObserver);
|
|
||||||
dataSources.unregisterContentObserver(pointObserver);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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) {
|
||||||
reloadDataForListener(registeredListeners.get(listener));
|
reloadDataForListener(listeners.getRegistration(listener));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -738,7 +481,7 @@ 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.
|
||||||
*/
|
*/
|
||||||
private void loadDataForAllListeners() {
|
private void loadDataForAllListeners() {
|
||||||
if (registeredListeners.isEmpty()) {
|
if (!listeners.hasListeners()) {
|
||||||
Log.d(TAG, "No listeners, not reloading");
|
Log.d(TAG, "No listeners, not reloading");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -751,7 +494,8 @@ public class TrackDataHub {
|
|||||||
@Override
|
@Override
|
||||||
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 : getListenersFor(ListenerDataType.DISPLAY_PREFERENCES)) {
|
for (TrackDataListener listener :
|
||||||
|
getListenersFor(ListenerDataType.DISPLAY_PREFERENCES)) {
|
||||||
listener.onUnitsChanged(useMetricUnits);
|
listener.onUnitsChanged(useMetricUnits);
|
||||||
listener.onReportSpeedChanged(reportSpeed);
|
listener.onReportSpeedChanged(reportSpeed);
|
||||||
}
|
}
|
||||||
@@ -809,12 +553,13 @@ public class TrackDataHub {
|
|||||||
runInListenerThread(new Runnable() {
|
runInListenerThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Set<TrackDataListener> listeners = getListenersFor(ListenerDataType.DISPLAY_PREFERENCES);
|
Set<TrackDataListener> displayListeners =
|
||||||
|
getListenersFor(ListenerDataType.DISPLAY_PREFERENCES);
|
||||||
|
|
||||||
for (TrackDataListener listener : listeners) {
|
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)) {
|
||||||
reloadDataForListener(registeredListeners.get(listener));
|
reloadDataForListener(listeners.getRegistration(listener));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -828,11 +573,11 @@ public class TrackDataHub {
|
|||||||
runInListenerThread(new Runnable() {
|
runInListenerThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Set<TrackDataListener> listeners = getListenersFor(ListenerDataType.DISPLAY_PREFERENCES);
|
Set<TrackDataListener> displayListeners = getListenersFor(ListenerDataType.DISPLAY_PREFERENCES);
|
||||||
|
|
||||||
for (TrackDataListener listener : listeners) {
|
for (TrackDataListener listener : displayListeners) {
|
||||||
if (listener.onUnitsChanged(useMetricUnits)) {
|
if (listener.onUnitsChanged(useMetricUnits)) {
|
||||||
reloadDataForListener(registeredListeners.get(listener));
|
reloadDataForListener(listeners.getRegistration(listener));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -860,7 +605,8 @@ public class TrackDataHub {
|
|||||||
public void run() {
|
public void run() {
|
||||||
// Notify to everyone.
|
// Notify to everyone.
|
||||||
Log.d(TAG, "Notifying fix type: " + state);
|
Log.d(TAG, "Notifying fix type: " + state);
|
||||||
for (TrackDataListener listener : getListenersFor(ListenerDataType.LOCATION_UPDATES)) {
|
for (TrackDataListener listener :
|
||||||
|
getListenersFor(ListenerDataType.LOCATION_UPDATES)) {
|
||||||
listener.onProviderStateChange(state);
|
listener.onProviderStateChange(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1094,8 +840,10 @@ public class TrackDataHub {
|
|||||||
firstSeenLocationId = -1;
|
firstSeenLocationId = -1;
|
||||||
lastSeenLocationId = -1;
|
lastSeenLocationId = -1;
|
||||||
numLoadedPoints = 0;
|
numLoadedPoints = 0;
|
||||||
sampledListeners = getListenersFor(ListenerDataType.POINT_UPDATES);
|
synchronized (listeners) {
|
||||||
sampledOutListeners = getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES);
|
sampledListeners = getListenersFor(ListenerDataType.POINT_UPDATES);
|
||||||
|
sampledOutListeners = getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES);
|
||||||
|
}
|
||||||
maxPointId = -1;
|
maxPointId = -1;
|
||||||
minPointId = 0;
|
minPointId = 0;
|
||||||
keepState = true;
|
keepState = true;
|
||||||
@@ -1209,14 +957,23 @@ public class TrackDataHub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<TrackDataListener> getListenersFor(ListenerDataType type) {
|
|
||||||
synchronized (registeredListeners) {
|
|
||||||
return listenerSetsPerType.get(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @VisibleForTesting
|
// @VisibleForTesting
|
||||||
protected void runInListenerThread(Runnable runnable) {
|
protected void runInListenerThread(Runnable runnable) {
|
||||||
listenerHandler.post(runnable);
|
listenerHandler.post(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Set<TrackDataListener> getListenersFor(ListenerDataType type) {
|
||||||
|
synchronized (listeners) {
|
||||||
|
return listeners.getListenersFor(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private EnumSet<ListenerDataType> getNeededListenerTypes() {
|
||||||
|
EnumSet<ListenerDataType> neededTypes = listeners.getAllRegisteredTypes();
|
||||||
|
|
||||||
|
// We always want preference updates.
|
||||||
|
neededTypes.add(ListenerDataType.DISPLAY_PREFERENCES);
|
||||||
|
|
||||||
|
return neededTypes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ import android.location.Location;
|
|||||||
/**
|
/**
|
||||||
* Listener for track data, for both initial and incremental loading.
|
* Listener for track data, for both initial and incremental loading.
|
||||||
*
|
*
|
||||||
* TODO: Allow listening to just part of the data received here.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public interface TrackDataListener {
|
public interface TrackDataListener {
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
// TODO: Add the last-notified point ID here, to allow pausing/resuming.
|
||||||
|
|
||||||
|
public ListenerRegistration(TrackDataListener listener,
|
||||||
|
EnumSet<ListenerDataType> types) {
|
||||||
|
this.listener = listener;
|
||||||
|
this.types = types;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInterestedIn(ListenerDataType type) {
|
||||||
|
return types.contains(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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 start}, and in that case
|
||||||
|
* the data will only be passed to listeners when {@link 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);
|
||||||
|
ListenerRegistration registration = new ListenerRegistration(listener, dataTypes);
|
||||||
|
|
||||||
|
if (registeredListeners.get(listener) != null) {
|
||||||
|
throw new IllegalStateException("Listener already registered");
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
|||||||
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.TrackDataListener;
|
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||||
import com.google.android.apps.mytracks.content.TrackDataSources;
|
import com.google.android.apps.mytracks.content.DataSourcesWrapper;
|
||||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||||
@@ -69,7 +69,8 @@ public class TrackDataHubTest extends AndroidTestCase {
|
|||||||
private static final long TRACK_ID = 42;
|
private static final long TRACK_ID = 42;
|
||||||
private MyTracksProviderUtils providerUtils;
|
private MyTracksProviderUtils providerUtils;
|
||||||
private TrackDataHub hub;
|
private TrackDataHub hub;
|
||||||
private TrackDataSources dataSources;
|
private TrackDataListeners listeners;
|
||||||
|
private DataSourcesWrapper dataSources;
|
||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
private TrackDataListener listener1;
|
private TrackDataListener listener1;
|
||||||
private TrackDataListener listener2;
|
private TrackDataListener listener2;
|
||||||
@@ -89,9 +90,10 @@ public class TrackDataHubTest extends AndroidTestCase {
|
|||||||
|
|
||||||
prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
||||||
providerUtils = AndroidMock.createMock("providerUtils", MyTracksProviderUtils.class);
|
providerUtils = AndroidMock.createMock("providerUtils", MyTracksProviderUtils.class);
|
||||||
dataSources = AndroidMock.createNiceMock("dataSources", TrackDataSources.class);
|
dataSources = AndroidMock.createNiceMock("dataSources", DataSourcesWrapper.class);
|
||||||
|
|
||||||
hub = new TrackDataHub(context, dataSources, prefs, providerUtils) {
|
listeners = new TrackDataListeners();
|
||||||
|
hub = new TrackDataHub(context, dataSources, listeners, prefs, providerUtils) {
|
||||||
@Override
|
@Override
|
||||||
protected void runInListenerThread(Runnable runnable) {
|
protected void runInListenerThread(Runnable runnable) {
|
||||||
// Run everything in the same thread.
|
// Run everything in the same thread.
|
||||||
|
|||||||
Reference in New Issue
Block a user