diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/DataSource.java b/MyTracks/src/com/google/android/apps/mytracks/content/DataSource.java new file mode 100644 index 000000000..5cda864d7 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/content/DataSource.java @@ -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); + } +} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/DataSourceListener.java b/MyTracks/src/com/google/android/apps/mytracks/content/DataSourceListener.java new file mode 100644 index 000000000..01cc1e7ef --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/content/DataSourceListener.java @@ -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); +} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/DataSourceManager.java b/MyTracks/src/com/google/android/apps/mytracks/content/DataSourceManager.java index 90c1e914d..26fdb7276 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/content/DataSourceManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/content/DataSourceManager.java @@ -1,3 +1,19 @@ +/* + * 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; @@ -10,7 +26,6 @@ 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; @@ -23,154 +38,178 @@ import java.util.EnumSet; 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 */ -class DataSourceManager { +public 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 notifyLocationProviderAvailable(boolean available); - void notifyLocationChanged(Location loc); - void notifyHeadingChanged(float heading); - } + /** + * Observer when the tracks table is updated. + * + * @author Jimmy Shih + */ + private class TracksTableObserver extends ContentObserver { - private final DataSourceListener listener; - - /** Observer for when the tracks table is updated. */ - private class TrackObserver extends ContentObserver { - public TrackObserver() { - super(contentHandler); + public TracksTableObserver() { + super(handler); } @Override public void onChange(boolean selfChange) { - listener.notifyTrackUpdated(); + dataSourceListener.notifyTracksTableUpdated(); } } - /** Observer for when the waypoints table is updated. */ - private class WaypointObserver extends ContentObserver { - public WaypointObserver() { - super(contentHandler); + /** + * Observer when the waypoints table is updated. + * + * @author Jimmy Shih + */ + private class WaypointsTableObserver extends ContentObserver { + + public WaypointsTableObserver() { + super(handler); } @Override public void onChange(boolean selfChange) { - listener.notifyWaypointUpdated(); + dataSourceListener.notifyWaypointsTableUpdated(); } } - /** Observer for when the points table is updated. */ - private class PointObserver extends ContentObserver { - public PointObserver() { - super(contentHandler); + /** + * Observer when the track points table is updated. + * + * @author Jimmy Shih + */ + private class TrackPointsTableObserver extends ContentObserver { + + public TrackPointsTableObserver() { + super(handler); } @Override public void onChange(boolean selfChange) { - listener.notifyPointsUpdated(); + dataSourceListener.notifyTrackPointsTableUpdated(); } } - /** 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.notifyLocationProviderAvailable(status == LocationProvider.AVAILABLE); - } + /** + * Listener for location changes. + * + * @author Jimmy Shih + */ + private class CurrentLocationListener implements LocationListener { @Override - public void onProviderEnabled(String provider) { - if (!LocationManager.GPS_PROVIDER.equals(provider)) return; - - listener.notifyLocationProviderEnabled(true); + public void onLocationChanged(Location location) { + dataSourceListener.notifyLocationChanged(location); } @Override public void onProviderDisabled(String provider) { - if (!LocationManager.GPS_PROVIDER.equals(provider)) return; - - listener.notifyLocationProviderEnabled(false); + if (!LocationManager.GPS_PROVIDER.equals(provider)) { + return; + } + dataSourceListener.notifyLocationProviderEnabled(false); } @Override - public void onLocationChanged(Location location) { - listener.notifyLocationChanged(location); + public void onProviderEnabled(String provider) { + 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 - SensorEventListener { - @Override - public void onSensorChanged(SensorEvent event) { - listener.notifyHeadingChanged(event.values[0]); - } + /** + * Listener for compass changes. + * + * @author Jimmy Shih + */ + private class CompassListener implements SensorEventListener { @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { // 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) - private final Set 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; + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + dataSourceListener.notifyPreferenceChanged(key); + } + } - DataSourceManager(DataSourceListener listener, DataSourcesWrapper dataSources) { - this.listener = listener; - this.dataSources = dataSources; + private final DataSource dataSource; + private final DataSourceListener dataSourceListener; - contentHandler = new Handler(); - pointObserver = new PointObserver(); - waypointObserver = new WaypointObserver(); - trackObserver = new TrackObserver(); + // Registered listeners + private final Set registeredListeners = EnumSet.noneOf(ListenerDataType.class); + + 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(); - locationListener = new CurrentLocationListener(); - preferenceListener = new HubSharedPreferenceListener(); + preferenceListener = new PreferenceListener(); } - /** Updates the internal (sensor, position, etc) listeners. */ - void updateAllListeners(EnumSet externallyNeededListeners) { - EnumSet neededListeners = EnumSet.copyOf(externallyNeededListeners); + /** + * Updates listeners with data source. + * + * @param listeners the listeners + */ + public void updateListeners(EnumSet listeners) { + EnumSet 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 + * 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); + Log.d(TAG, "Updating listeners " + neededListeners); // Unnecessary = registered - needed Set unnecessaryListeners = EnumSet.copyOf(registeredListeners); @@ -180,86 +219,100 @@ class DataSourceManager { Set missingListeners = EnumSet.copyOf(neededListeners); missingListeners.removeAll(registeredListeners); - // Remove all unnecessary listeners. + // Remove unnecessary listeners for (ListenerDataType type : unnecessaryListeners) { unregisterListener(type); } - // Add all missing listeners. + // Add missing listeners for (ListenerDataType type : missingListeners) { registerListener(type); } - // Now all needed types are registered. + // Update registered listeners registeredListeners.clear(); registeredListeners.addAll(neededListeners); } + /** + * Registers a listener with data source. + * + * @param type the listener data type + */ 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); + case SELECTED_TRACK_CHANGED: + // Do nothing break; case TRACK_UPDATES: - dataSources.registerContentObserver(TracksColumns.CONTENT_URI, false, trackObserver); + dataSource.registerContentObserver(TracksColumns.CONTENT_URI, tracksTableObserver); break; case WAYPOINT_UPDATES: - dataSources.registerContentObserver( - WaypointsColumns.CONTENT_URI, false, waypointObserver); + dataSource.registerContentObserver(WaypointsColumns.CONTENT_URI, waypointsTableObserver); break; - case DISPLAY_PREFERENCES: - dataSources.registerOnSharedPreferenceChangeListener(preferenceListener); + case POINT_UPDATES: + dataSource.registerContentObserver( + TrackPointsColumns.CONTENT_URI, trackPointsTableObserver); break; case SAMPLED_OUT_POINT_UPDATES: - throw new IllegalArgumentException("Should have been mapped to point updates"); + // Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES. + break; + case LOCATION_UPDATES: + dataSource.registerLocationListener(currentLocationListener); + break; + case COMPASS_UPDATES: + dataSource.registerCompassListener(compassListener); + break; + case DISPLAY_PREFERENCES: + dataSource.registerOnSharedPreferenceChangeListener(preferenceListener); + break; + default: + break; } } + /** + * Unregisters a listener with data source. + * + * @param type listener data type + */ 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); + case SELECTED_TRACK_CHANGED: + // Do nothing break; case TRACK_UPDATES: - dataSources.unregisterContentObserver(trackObserver); + dataSource.unregisterContentObserver(tracksTableObserver); break; case WAYPOINT_UPDATES: - dataSources.unregisterContentObserver(waypointObserver); + dataSource.unregisterContentObserver(waypointsTableObserver); break; - case DISPLAY_PREFERENCES: - dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListener); + case POINT_UPDATES: + dataSource.unregisterContentObserver(trackPointsTableObserver); break; case SAMPLED_OUT_POINT_UPDATES: - throw new IllegalArgumentException("Should have been mapped to point updates"); + // Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES. + break; + case LOCATION_UPDATES: + dataSource.unregisterLocationListener(currentLocationListener); + break; + case COMPASS_UPDATES: + dataSource.unregisterCompassListener(compassListener); + break; + case DISPLAY_PREFERENCES: + dataSource.unregisterOnSharedPreferenceChangeListener(preferenceListener); + break; + default: + break; } } - /** 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); + /** + * Unregisters all listeners with data source. + */ + public void unregisterAllListeners() { + for (ListenerDataType type : ListenerDataType.values()) { + unregisterListener(type); + } } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/DataSourcesWrapper.java b/MyTracks/src/com/google/android/apps/mytracks/content/DataSourcesWrapper.java deleted file mode 100644 index a4dbf3544..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/content/DataSourcesWrapper.java +++ /dev/null @@ -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(); -} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/DataSourcesWrapperImpl.java b/MyTracks/src/com/google/android/apps/mytracks/content/DataSourcesWrapperImpl.java deleted file mode 100644 index 9bc57f08c..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/content/DataSourcesWrapperImpl.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java index fc5b8b7cd..dc12c5fce 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java +++ b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java @@ -21,8 +21,6 @@ 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.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.LocationIterator; import com.google.android.apps.mytracks.content.TrackDataListener.ProviderState; @@ -32,7 +30,6 @@ import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.maps.mytracks.R; import android.content.Context; -import android.content.SharedPreferences; import android.database.Cursor; import android.hardware.GeomagneticField; import android.location.Location; @@ -94,17 +91,17 @@ public class TrackDataHub { /** Listener which receives events from the system. */ private class HubDataSourceListener implements DataSourceListener { @Override - public void notifyTrackUpdated() { + public void notifyTracksTableUpdated() { TrackDataHub.this.notifyTrackUpdated(getListenersFor(ListenerDataType.TRACK_UPDATES)); } @Override - public void notifyWaypointUpdated() { + public void notifyWaypointsTableUpdated() { TrackDataHub.this.notifyWaypointUpdated(getListenersFor(ListenerDataType.WAYPOINT_UPDATES)); } @Override - public void notifyPointsUpdated() { + public void notifyTrackPointsTableUpdated() { TrackDataHub.this.notifyPointsUpdated(true, 0, 0, getListenersFor(ListenerDataType.POINT_UPDATES), getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES)); @@ -144,7 +141,6 @@ public class TrackDataHub { // Application services private final Context context; private final MyTracksProviderUtils providerUtils; - private final SharedPreferences preferences; // Get content notifications on the main thread, send listener callbacks in another. // This ensures listener calls are serialized. @@ -154,8 +150,7 @@ public class TrackDataHub { /** Manager for external listeners (those from activities). */ private final TrackDataListeners dataListeners; - /** Wrapper for interacting with system data managers. */ - private DataSourcesWrapper dataSources; + private DataSource dataSource; /** Manager for system data listener registrations. */ private DataSourceManager dataSourceManager; @@ -193,24 +188,19 @@ public class TrackDataHub { * Builds a new {@link TrackDataHub} instance. */ public synchronized static TrackDataHub newInstance(Context context) { - SharedPreferences preferences = context.getSharedPreferences( - Constants.SETTINGS_NAME, Context.MODE_PRIVATE); MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(context); - return new TrackDataHub(context, - new TrackDataListeners(), - preferences, providerUtils, - TARGET_DISPLAYED_TRACK_POINTS); + return new TrackDataHub( + context, new TrackDataListeners(), providerUtils, TARGET_DISPLAYED_TRACK_POINTS); } /** * Injection constructor. */ // @VisibleForTesting - TrackDataHub(Context ctx, TrackDataListeners listeners, SharedPreferences preferences, - MyTracksProviderUtils providerUtils, int targetNumPoints) { + TrackDataHub(Context ctx, TrackDataListeners listeners, MyTracksProviderUtils providerUtils, + int targetNumPoints) { this.context = ctx; this.dataListeners = listeners; - this.preferences = preferences; this.providerUtils = providerUtils; this.targetNumPoints = targetNumPoints; this.locationFactory = new DoubleBufferedLocationFactory(); @@ -233,12 +223,12 @@ public class TrackDataHub { listenerHandlerThread = new HandlerThread("trackDataContentThread"); listenerHandlerThread.start(); listenerHandler = new Handler(listenerHandlerThread.getLooper()); - dataSources = newDataSources(); - dataSourceManager = new DataSourceManager(dataSourceListener, dataSources); + dataSource = newDataSource(); + dataSourceManager = new DataSourceManager(dataSource, dataSourceListener); // This may or may not register internal listeners, depending on whether // we already had external listeners. - dataSourceManager.updateAllListeners(getNeededListenerTypes()); + dataSourceManager.updateListeners(getNeededListenerTypes()); loadSharedPreferences(); // If there were listeners already registered, make sure they become up-to-date. @@ -246,8 +236,8 @@ public class TrackDataHub { } // @VisibleForTesting - protected DataSourcesWrapper newDataSources() { - return new DataSourcesWrapperImpl(context, preferences); + protected DataSource newDataSource() { + return new DataSource(context); } /** @@ -267,7 +257,7 @@ public class TrackDataHub { started = false; - dataSources = null; + dataSource = null; dataSourceManager = null; listenerHandlerThread = null; listenerHandler = null; @@ -343,7 +333,7 @@ public class TrackDataHub { } Log.i(TAG, "Forcing location update"); - Location loc = dataSources.getLastKnownLocation(); + Location loc = dataSource.getLastKnownLocation(); if (loc != null) { notifyLocationChanged(loc, getListenersFor(ListenerDataType.LOCATION_UPDATES)); } @@ -426,7 +416,7 @@ public class TrackDataHub { loadNewDataForListener(registration); - dataSourceManager.updateAllListeners(getNeededListenerTypes()); + dataSourceManager.updateListeners(getNeededListenerTypes()); } } @@ -438,7 +428,7 @@ public class TrackDataHub { // called. When it is called, we'll do both things. if (!isStarted()) return; - dataSourceManager.updateAllListeners(getNeededListenerTypes()); + dataSourceManager.updateListeners(getNeededListenerTypes()); } } diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/content/TrackDataHubTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/content/TrackDataHubTest.java index da068091d..cef9e584d 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/content/TrackDataHubTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/content/TrackDataHubTest.java @@ -15,13 +15,11 @@ */ 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.eq; 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.leq; -import static com.google.android.testing.mocking.AndroidMock.same; import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationFactory; @@ -32,6 +30,7 @@ import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockC import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.maps.mytracks.R; import com.google.android.testing.mocking.AndroidMock; +import com.google.android.testing.mocking.UsesMocks; import android.content.Context; import android.content.SharedPreferences; @@ -70,7 +69,7 @@ public class TrackDataHubTest extends AndroidTestCase { private MyTracksProviderUtils providerUtils; private TrackDataHub hub; private TrackDataListeners listeners; - private DataSourcesWrapper dataSources; + private DataSource dataSource; private SharedPreferences sharedPreferences; private TrackDataListener listener1; private TrackDataListener listener2; @@ -79,6 +78,7 @@ public class TrackDataHubTest extends AndroidTestCase { private MockContext context; private float declination; + @UsesMocks({MyTracksProviderUtils.class, DataSource.class, TrackDataListener.class}) @Override protected void setUp() throws Exception { super.setUp(); @@ -90,13 +90,13 @@ public class TrackDataHubTest extends AndroidTestCase { sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); providerUtils = AndroidMock.createMock("providerUtils", MyTracksProviderUtils.class); - dataSources = AndroidMock.createNiceMock("dataSources", DataSourcesWrapper.class); + dataSource = AndroidMock.createMock("dataSource", DataSource.class, context); listeners = new TrackDataListeners(); - hub = new TrackDataHub(context, listeners, sharedPreferences, providerUtils, TARGET_POINTS) { + hub = new TrackDataHub(context, listeners, providerUtils, TARGET_POINTS) { @Override - protected DataSourcesWrapper newDataSources() { - return dataSources; + protected DataSource newDataSource() { + return dataSource; } @Override @@ -119,18 +119,18 @@ public class TrackDataHubTest extends AndroidTestCase { @Override protected void tearDown() throws Exception { - AndroidMock.reset(dataSources); + AndroidMock.reset(dataSource); // Expect everything to be unregistered. if (preferenceListenerCapture.hasCaptured()) { - dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListenerCapture.getValue()); + dataSource.unregisterOnSharedPreferenceChangeListener(preferenceListenerCapture.getValue()); } - dataSources.removeLocationUpdates(isA(LocationListener.class)); - dataSources.unregisterSensorListener(isA(SensorEventListener.class)); - dataSources.unregisterContentObserver(isA(ContentObserver.class)); + dataSource.unregisterLocationListener(isA(LocationListener.class)); + dataSource.unregisterCompassListener(isA(SensorEventListener.class)); + dataSource.unregisterContentObserver(isA(ContentObserver.class)); AndroidMock.expectLastCall().times(3); - AndroidMock.replay(dataSources); + AndroidMock.replay(dataSource); hub.stop(); hub = null; @@ -143,8 +143,7 @@ public class TrackDataHubTest extends AndroidTestCase { Track track = new Track(); expect(providerUtils.getTrack(TRACK_ID)).andStubReturn(track); expectStart(); - dataSources.registerContentObserver( - eq(TracksColumns.CONTENT_URI), eq(false), capture(observerCapture)); + dataSource.registerContentObserver(eq(TracksColumns.CONTENT_URI), capture(observerCapture)); // Expect the initial loading. // Both listeners (registered before and after start) should get the same data. @@ -185,7 +184,7 @@ public class TrackDataHubTest extends AndroidTestCase { verifyAndReset(); // Unregister the other, expect internal unregistration - dataSources.unregisterContentObserver(observer); + dataSource.unregisterContentObserver(observer); replay(); @@ -321,8 +320,7 @@ public class TrackDataHubTest extends AndroidTestCase { .andReturn(wpt2); expectStart(); - dataSources.registerContentObserver( - eq(WaypointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); + dataSource.registerContentObserver(eq(WaypointsColumns.CONTENT_URI), capture(observerCapture)); // Expect the initial loading. // Both listeners (registered before and after start) should get the same data. @@ -398,7 +396,7 @@ public class TrackDataHubTest extends AndroidTestCase { verifyAndReset(); // Unregister the other, expect internal unregistration - dataSources.unregisterContentObserver(observer); + dataSource.unregisterContentObserver(observer); replay(); @@ -412,8 +410,8 @@ public class TrackDataHubTest extends AndroidTestCase { Capture observerCapture = new Capture(); expectStart(); - dataSources.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture)); FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5); expect(providerUtils.getLocationIterator( @@ -497,8 +495,8 @@ public class TrackDataHubTest extends AndroidTestCase { Capture observerCapture = new Capture(); expectStart(); - dataSources.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture)); FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5); expect(providerUtils.getLocationIterator( @@ -519,7 +517,7 @@ public class TrackDataHubTest extends AndroidTestCase { // Unregister ContentObserver observer = observerCapture.getValue(); - dataSources.unregisterContentObserver(observer); + dataSource.unregisterContentObserver(observer); replay(); @@ -528,8 +526,8 @@ public class TrackDataHubTest extends AndroidTestCase { verifyAndReset(); // Register again, except only points since unregistered. - dataSources.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture)); locationIterator = new FixedSizeLocationIterator(1, 10, 5); expect(providerUtils.getLocationIterator( @@ -552,8 +550,8 @@ public class TrackDataHubTest extends AndroidTestCase { Capture observerCapture = new Capture(); expectStart(); - dataSources.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture)); FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5); expect(providerUtils.getLocationIterator( @@ -574,7 +572,7 @@ public class TrackDataHubTest extends AndroidTestCase { // Unregister ContentObserver observer = observerCapture.getValue(); - dataSources.unregisterContentObserver(observer); + dataSource.unregisterContentObserver(observer); replay(); @@ -583,8 +581,8 @@ public class TrackDataHubTest extends AndroidTestCase { verifyAndReset(); // Register again after track changed, expect all points. - dataSources.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture)); locationIterator = new FixedSizeLocationIterator(1, 10); expect(providerUtils.getLocationIterator( @@ -608,8 +606,8 @@ public class TrackDataHubTest extends AndroidTestCase { Capture observerCapture = new Capture(); expectStart(); - dataSources.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture)); FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 200, 4, 25, 71, 120); expect(providerUtils.getLocationIterator( @@ -639,8 +637,8 @@ public class TrackDataHubTest extends AndroidTestCase { Capture observerCapture = new Capture(); expectStart(); - dataSources.registerContentObserver( - eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture)); + dataSource.registerContentObserver( + eq(TrackPointsColumns.CONTENT_URI), capture(observerCapture)); // Deliver 30 points (no sampling happens) FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 30, 5); @@ -702,16 +700,14 @@ public class TrackDataHubTest extends AndroidTestCase { public void testCompassListen() throws Exception { AndroidMock.resetToDefault(listener1); - Sensor compass = newSensor(); - expect(dataSources.getSensor(Sensor.TYPE_ORIENTATION)).andReturn(compass); + expectStart(); Capture listenerCapture = new Capture(); - dataSources.registerSensorListener(capture(listenerCapture), same(compass), anyInt()); + dataSource.registerCompassListener(capture(listenerCapture)); Capture locationListenerCapture = new Capture(); - dataSources.requestLocationUpdates(capture(locationListenerCapture)); + dataSource.registerLocationListener(capture(locationListenerCapture)); SensorEvent event = newSensorEvent(); - event.sensor = compass; // First, get a dummy heading update. listener1.onCurrentHeadingChanged(0.0); @@ -786,9 +782,7 @@ public class TrackDataHubTest extends AndroidTestCase { PreferencesUtils.setBoolean(context, R.string.report_speed_key, true); PreferencesUtils.setBoolean(context, R.string.metric_units_key, true); - Capture listenerCapture = - new Capture(); - dataSources.registerOnSharedPreferenceChangeListener(capture(listenerCapture)); + expectStart(); expect(listener1.onUnitsChanged(true)).andReturn(false); expect(listener2.onUnitsChanged(true)).andReturn(false); @@ -809,12 +803,12 @@ public class TrackDataHubTest extends AndroidTestCase { replay(); PreferencesUtils.setBoolean(context, R.string.report_speed_key, false); - OnSharedPreferenceChangeListener listener = listenerCapture.getValue(); + OnSharedPreferenceChangeListener listener = preferenceListenerCapture.getValue(); listener.onSharedPreferenceChanged( sharedPreferences, PreferencesUtils.getKey(context, R.string.report_speed_key)); - AndroidMock.verify(dataSources, providerUtils, listener1, listener2); - AndroidMock.reset(dataSources, providerUtils, listener1, listener2); + AndroidMock.verify(dataSource, providerUtils, listener1, listener2); + AndroidMock.reset(dataSource, providerUtils, listener1, listener2); expect(listener1.onUnitsChanged(false)).andReturn(false); expect(listener2.onUnitsChanged(false)).andReturn(false); @@ -829,15 +823,15 @@ public class TrackDataHubTest extends AndroidTestCase { } private void expectStart() { - dataSources.registerOnSharedPreferenceChangeListener(capture(preferenceListenerCapture)); + dataSource.registerOnSharedPreferenceChangeListener(capture(preferenceListenerCapture)); } private void replay() { - AndroidMock.replay(dataSources, providerUtils, listener1, listener2); + AndroidMock.replay(dataSource, providerUtils, listener1, listener2); } private void verifyAndReset() { - AndroidMock.verify(listener1, listener2, dataSources, providerUtils); - AndroidMock.reset(listener1, listener2, dataSources, providerUtils); + AndroidMock.verify(listener1, listener2, dataSource, providerUtils); + AndroidMock.reset(listener1, listener2, dataSource, providerUtils); } }