Merge heads

This commit is contained in:
youtaol
2012-07-20 17:06:17 +08:00
104 changed files with 2666 additions and 2602 deletions
+55 -3
View File
@@ -1,4 +1,4 @@
#Fri Oct 21 20:06:28 BRST 2011
#Tue Jul 17 10:23:26 PDT 2012
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
@@ -76,29 +76,66 @@ org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.source=1.6
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field=1585
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field.count_dependent=1585|-1|1585
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable=1585
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable.count_dependent=1585|-1|1585
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method=1585
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method.count_dependent=1585|-1|1585
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package=1585
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package.count_dependent=1585|-1|1585
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter=1040
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter.count_dependent=1040|-1|1040
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type=1585
org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type.count_dependent=1585|-1|1585
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=48
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
org.eclipse.jdt.core.formatter.alignment_for_binary_expression.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_cascading_method_invocation_with_arguments=16
org.eclipse.jdt.core.formatter.alignment_for_cascading_method_invocation_with_arguments.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=48
org.eclipse.jdt.core.formatter.alignment_for_enum_constants.count_dependent=0|-1|0
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_field_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_for_statement=16
org.eclipse.jdt.core.formatter.alignment_for_generic_type_arguments=16
org.eclipse.jdt.core.formatter.alignment_for_generic_type_arguments.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_local_variable_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_new_anonymous_class=0
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=48
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration.count_dependent=16|-1|16
org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
@@ -138,12 +175,14 @@ org.eclipse.jdt.core.formatter.comment.line_length=80
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
org.eclipse.jdt.core.formatter.comment_new_line_at_start_of_html_paragraph=true
org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=2
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=true
org.eclipse.jdt.core.formatter.force_if_else_statement_brace=false
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
@@ -210,6 +249,7 @@ org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_re
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comment_prefix=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
@@ -340,7 +380,7 @@ org.eclipse.jdt.core.formatter.join_lines_in_comments=true
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.lineSplit=100
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
@@ -348,10 +388,22 @@ org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
org.eclipse.jdt.core.formatter.sort_local_variable_annotations=false
org.eclipse.jdt.core.formatter.sort_member_annotations=false
org.eclipse.jdt.core.formatter.sort_package_annotations=false
org.eclipse.jdt.core.formatter.sort_parameter_annotations=false
org.eclipse.jdt.core.formatter.sort_type_annotations=false
org.eclipse.jdt.core.formatter.tabulation.char=space
org.eclipse.jdt.core.formatter.tabulation.size=2
org.eclipse.jdt.core.formatter.use_on_off_tags=false
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
org.eclipse.jdt.core.formatter.wrap_comment_inline_tags=false
org.eclipse.jdt.core.formatter.wrap_non_simple_local_variable_annotation=true
org.eclipse.jdt.core.formatter.wrap_non_simple_member_annotation=true
org.eclipse.jdt.core.formatter.wrap_non_simple_package_annotation=true
org.eclipse.jdt.core.formatter.wrap_non_simple_parameter_annotation=false
org.eclipse.jdt.core.formatter.wrap_non_simple_type_annotation=true
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
org.eclipse.jdt.core.formatter.wrap_prefer_two_fragments=false
+3 -3
View File
@@ -1,8 +1,8 @@
#Thu Jul 21 03:04:13 BRT 2011
#Tue Jul 17 10:22:06 PDT 2012
eclipse.preferences.version=1
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
formatter_profile=_MyTracks
formatter_settings_version=12
formatter_profile=_My Tracks
formatter_settings_version=13
org.eclipse.jdt.ui.ignorelowercasenames=true
org.eclipse.jdt.ui.importorder=com;android;java;javax;org;
org.eclipse.jdt.ui.ondemandthreshold=99
+1 -1
View File
@@ -16,7 +16,7 @@ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="auto"
android:versionCode="47"
android:versionCode="48"
android:versionName="2.0"
package="com.google.android.maps.mytracks" >
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -30,7 +30,7 @@
other activity while watching live performance statistics. My Tracks makes it easy to archive
your training history with Google Maps, Google Fusion Tables, and Google Docs, and share your
activities with friends and the world.</string><string name="help_my_tracks_question" translation_description="In the 'Help' page, the question on what is My Tracks.">What is My Tracks?</string><string name="help_saved_files_answer" translation_description="In the 'Help' page, the answer on saved files.">You can use Google Earth or similar applications to visualize and edit GPS tracks.</string><string name="help_saved_files_question" translation_description="In the 'Help' page, the question on saved files.">What can I do with tracks after saving them to the SD card or sharing such files with friends?</string><string name="help_send_track_answer" translation_description="In the 'Help' page, the answer on sending a track. %1$s url. E.g., 'maps.google.com'. %2$s url. E.g., 'google.com/fusiontables'. %3$s url. E.g., 'docs.google.com'.">You can send a track to Google Maps, Google Fusion Tables, and Google Docs. Tracks sent to
Google Maps are at %1$s, \\"My Places\\". Tracks sent to Google Fusion Tables are at %2$s. Tracks
Google Maps are at %1$s, \"My Places\". Tracks sent to Google Fusion Tables are at %2$s. Tracks
sent to Google Docs are at %3$s.</string><string name="help_send_track_question" translation_description="In the 'Help' page, the question on sending a track.">Where can I send a track?</string><string name="help_share_friends_answer" translation_description="In the 'Help' page, the answer on sharing with friends.">You can share a track as a link to Google Maps. Your track will first get uploaded to Google
Maps.</string><string name="help_share_friends_question" translation_description="In the 'Help' page, the question on sharing with friends.">How do I share a track with friends?</string><string name="help_share_visibility_answer" translation_description="In the 'Help' page, the answer on share visibility.">By default, tracks sent to Google Maps are unlisted, tracks sent to Google Fusion Tables are
public, and tracks sent to Google Docs are private. For Google Maps, you can change the
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,192 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.Constants.MAX_LOCATION_AGE_MS;
import com.google.android.apps.mytracks.Constants;
import com.google.android.maps.mytracks.R;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
/**
* Data source on the phone.
*
* @author Rodrigo Damazio
*/
public class DataSource {
private static final int NETWORK_PROVIDER_MIN_TIME = 5 * 60 * 1000; // 5 minutes
private static final String TAG = DataSource.class.getSimpleName();
private final Context context;
private final ContentResolver contentResolver;
private final LocationManager locationManager;
private final SensorManager sensorManager;
private final SharedPreferences sharedPreferences;
public DataSource(Context context) {
this.context = context;
contentResolver = context.getContentResolver();
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
sharedPreferences = context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
}
/**
* Registers a content observer.
*
* @param uri the uri
* @param observer the observer
*/
public void registerContentObserver(Uri uri, ContentObserver observer) {
contentResolver.registerContentObserver(uri, false, observer);
}
/**
* Unregisters a content observer.
*
* @param observer the observer
*/
public void unregisterContentObserver(ContentObserver observer) {
contentResolver.unregisterContentObserver(observer);
}
/**
* Registers a location listener.
*
* @param listener the listener
*/
public void registerLocationListener(LocationListener listener) {
// Check if the GPS provider exists
if (locationManager.getProvider(LocationManager.GPS_PROVIDER) == null) {
listener.onProviderDisabled(LocationManager.GPS_PROVIDER);
unregisterLocationListener(listener);
return;
}
// Listen for GPS location
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
// Update the listener with the current provider state
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
listener.onProviderEnabled(LocationManager.GPS_PROVIDER);
} else {
listener.onProviderDisabled(LocationManager.GPS_PROVIDER);
}
// Listen for network location
try {
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, NETWORK_PROVIDER_MIN_TIME, 0, listener);
} catch (RuntimeException e) {
// Network location is optional, so just log the exception
Log.w(TAG, "Could not register for network location.", e);
}
}
/**
* Unregisters a location listener.
*
* @param listener the listener
*/
public void unregisterLocationListener(LocationListener listener) {
locationManager.removeUpdates(listener);
}
/**
* Gets the last known location.
*/
public Location getLastKnownLocation() {
Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (!isLocationRecent(location)) {
// Try network location
location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
int id = isLocationRecent(location)
? R.string.my_location_approximate_location : R.string.my_location_no_location;
Toast.makeText(context, id, Toast.LENGTH_LONG).show();
}
return location;
}
/**
* Returns true if the location is recent.
*
* @param location the location
*/
private boolean isLocationRecent(Location location) {
if (location == null) {
return false;
}
return location.getTime() > System.currentTimeMillis() - MAX_LOCATION_AGE_MS;
}
/**
* Registers a compass listener.
*
* @param listener the listener
*/
public void registerCompassListener(SensorEventListener listener) {
Sensor compass = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
if (compass == null) {
Log.d(TAG, "No compass sensor.");
return;
}
sensorManager.registerListener(listener, compass, SensorManager.SENSOR_DELAY_UI);
}
/**
* Unregisters a compass listener.
*
* @param listener the listener
*/
public void unregisterCompassListener(SensorEventListener listener) {
sensorManager.unregisterListener(listener);
}
/**
* Registers a shared preference change listener.
*
* @param listener the listener
*/
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
}
/**
* Unregisters a shared preference change listener.
*
* @param listener the listener
*/
public void unregisterOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener) {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
}
}
@@ -0,0 +1,78 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
import android.location.Location;
/**
* Listener to be invoked when {@link DataSource} changes.
*
* @author Jimmy Shih
*/
public interface DataSourceListener {
/**
* Notifies when the tracks table is updated.
*/
public void notifyTracksTableUpdated();
/**
* Notifies when the waypoints table is updated.
*/
public void notifyWaypointsTableUpdated();
/**
* Notifies when the track points table is updated.
*/
public void notifyTrackPointsTableUpdated();
/**
* Notifies when the current location changes.
*
* @param location the location
*/
public void notifyLocationChanged(Location location);
/**
* Notifies when the location provider enabled status changes.
*
* @param enabled true if enabled, false if not
*/
public void notifyLocationProviderEnabled(boolean enabled);
/**
* Notifies when the location provider availability status changes.
*
* @param available true if available, false if not
*/
public void notifyLocationProviderAvailable(boolean available);
/**
* Notifies when the compass heading changes.
*
* @param heading the new heading
*/
public void notifyHeadingChanged(float heading);
/**
* Notifies when a preference changes.
*
* @param key the preference key
*/
public void notifyPreferenceChanged(String key);
}
@@ -1,16 +1,29 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
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;
@@ -23,243 +36,281 @@ 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<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;
@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<TrackDataType> registeredListeners = EnumSet.noneOf(TrackDataType.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<ListenerDataType> externallyNeededListeners) {
EnumSet<ListenerDataType> neededListeners = EnumSet.copyOf(externallyNeededListeners);
/**
* Updates listeners with data source.
*
* @param listeners the listeners
*/
public void updateListeners(EnumSet<TrackDataType> listeners) {
EnumSet<TrackDataType> neededListeners = EnumSet.copyOf(listeners);
// Special case - map sampled-out points type to points type since they
// correspond to the same internal listener.
if (neededListeners.contains(ListenerDataType.SAMPLED_OUT_POINT_UPDATES)) {
neededListeners.remove(ListenerDataType.SAMPLED_OUT_POINT_UPDATES);
neededListeners.add(ListenerDataType.POINT_UPDATES);
/*
* Map SAMPLED_OUT_POINT_UPDATES to POINT_UPDATES since they correspond to
* the same internal listener
*/
if (neededListeners.contains(TrackDataType.SAMPLED_OUT_TRACK_POINTS)) {
neededListeners.remove(TrackDataType.SAMPLED_OUT_TRACK_POINTS);
neededListeners.add(TrackDataType.TRACK_POINTS_TABLE);
}
Log.d(TAG, "Updating internal listeners to types " + neededListeners);
Log.d(TAG, "Updating listeners " + neededListeners);
// Unnecessary = registered - needed
Set<ListenerDataType> unnecessaryListeners = EnumSet.copyOf(registeredListeners);
Set<TrackDataType> unnecessaryListeners = EnumSet.copyOf(registeredListeners);
unnecessaryListeners.removeAll(neededListeners);
// Missing = needed - registered
Set<ListenerDataType> missingListeners = EnumSet.copyOf(neededListeners);
Set<TrackDataType> missingListeners = EnumSet.copyOf(neededListeners);
missingListeners.removeAll(registeredListeners);
// Remove all unnecessary listeners.
for (ListenerDataType type : unnecessaryListeners) {
unregisterListener(type);
// Remove unnecessary listeners
for (TrackDataType trackDataType : unnecessaryListeners) {
unregisterListener(trackDataType);
}
// Add all missing listeners.
for (ListenerDataType type : missingListeners) {
registerListener(type);
// Add missing listeners
for (TrackDataType trackDataType : missingListeners) {
registerListener(trackDataType);
}
// Now all needed types are registered.
// Update registered listeners
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);
}
/**
* Registers a listener with data source.
*
* @param trackDataType the listener data type
*/
private void registerListener(TrackDataType trackDataType) {
switch (trackDataType) {
case SELECTED_TRACK:
// Do nothing
break;
}
case LOCATION_UPDATES:
dataSources.requestLocationUpdates(locationListener);
case TRACKS_TABLE:
dataSource.registerContentObserver(TracksColumns.CONTENT_URI, tracksTableObserver);
break;
case POINT_UPDATES:
dataSources.registerContentObserver(
TrackPointsColumns.CONTENT_URI, false, pointObserver);
case WAYPOINTS_TABLE:
dataSource.registerContentObserver(WaypointsColumns.CONTENT_URI, waypointsTableObserver);
break;
case TRACK_UPDATES:
dataSources.registerContentObserver(TracksColumns.CONTENT_URI, false, trackObserver);
case TRACK_POINTS_TABLE:
dataSource.registerContentObserver(
TrackPointsColumns.CONTENT_URI, trackPointsTableObserver);
break;
case WAYPOINT_UPDATES:
dataSources.registerContentObserver(
WaypointsColumns.CONTENT_URI, false, waypointObserver);
case SAMPLED_OUT_TRACK_POINTS:
// Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES.
break;
case DISPLAY_PREFERENCES:
dataSources.registerOnSharedPreferenceChangeListener(preferenceListener);
case LOCATION:
dataSource.registerLocationListener(currentLocationListener);
break;
case COMPASS:
dataSource.registerCompassListener(compassListener);
break;
case PREFERENCE:
dataSource.registerOnSharedPreferenceChangeListener(preferenceListener);
break;
default:
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);
/**
* Unregisters a listener with data source.
*
* @param trackDataType listener data type
*/
private void unregisterListener(TrackDataType trackDataType) {
switch (trackDataType) {
case SELECTED_TRACK:
// Do nothing
break;
case LOCATION_UPDATES:
dataSources.removeLocationUpdates(locationListener);
case TRACKS_TABLE:
dataSource.unregisterContentObserver(tracksTableObserver);
break;
case POINT_UPDATES:
dataSources.unregisterContentObserver(pointObserver);
case WAYPOINTS_TABLE:
dataSource.unregisterContentObserver(waypointsTableObserver);
break;
case TRACK_UPDATES:
dataSources.unregisterContentObserver(trackObserver);
case TRACK_POINTS_TABLE:
dataSource.unregisterContentObserver(trackPointsTableObserver);
break;
case WAYPOINT_UPDATES:
dataSources.unregisterContentObserver(waypointObserver);
case SAMPLED_OUT_TRACK_POINTS:
// Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES.
break;
case DISPLAY_PREFERENCES:
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListener);
case LOCATION:
dataSource.unregisterLocationListener(currentLocationListener);
break;
case COMPASS:
dataSource.unregisterCompassListener(compassListener);
break;
case PREFERENCE:
dataSource.unregisterOnSharedPreferenceChangeListener(preferenceListener);
break;
default:
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);
/**
* Unregisters all listeners with data source.
*/
public void unregisterAllListeners() {
for (TrackDataType trackDataType : TrackDataType.values()) {
unregisterListener(trackDataType);
}
}
}
@@ -1,54 +0,0 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.location.Location;
import android.location.LocationListener;
import android.net.Uri;
/**
* Interface for abstracting registration of external data source listeners.
*
* @author Rodrigo Damazio
*/
interface DataSourcesWrapper {
// Preferences
void registerOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener);
void unregisterOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener);
// Content provider
void registerContentObserver(Uri contentUri, boolean descendents,
ContentObserver observer);
void unregisterContentObserver(ContentObserver observer);
// Sensors
Sensor getSensor(int type);
void registerSensorListener(SensorEventListener listener,
Sensor sensor, int sensorDelay);
void unregisterSensorListener(SensorEventListener listener);
// Location
boolean isLocationProviderEnabled(String provider);
void requestLocationUpdates(LocationListener listener);
void removeLocationUpdates(LocationListener listener);
Location getLastKnownLocation();
}
@@ -1,166 +0,0 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.Constants.MAX_LOCATION_AGE_MS;
import static com.google.android.apps.mytracks.Constants.MAX_NETWORK_AGE_MS;
import com.google.android.apps.mytracks.Constants;
import com.google.android.maps.mytracks.R;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
/**
* Real implementation of the data sources, which talks to system services.
*
* @author Rodrigo Damazio
*/
class DataSourcesWrapperImpl implements DataSourcesWrapper {
// System services
private final SensorManager sensorManager;
private final LocationManager locationManager;
private final ContentResolver contentResolver;
private final SharedPreferences sharedPreferences;
private final Context context;
DataSourcesWrapperImpl(Context context, SharedPreferences sharedPreferences) {
this.context = context;
this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
this.contentResolver = context.getContentResolver();
this.sharedPreferences = sharedPreferences;
}
@Override
public void registerOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener) {
sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
}
@Override
public void unregisterOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener) {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
}
@Override
public void registerContentObserver(Uri contentUri, boolean descendents,
ContentObserver observer) {
contentResolver.registerContentObserver(contentUri, descendents, observer);
}
@Override
public void unregisterContentObserver(ContentObserver observer) {
contentResolver.unregisterContentObserver(observer);
}
@Override
public Sensor getSensor(int type) {
return sensorManager.getDefaultSensor(type);
}
@Override
public void registerSensorListener(SensorEventListener listener,
Sensor sensor, int sensorDelay) {
sensorManager.registerListener(listener, sensor, sensorDelay);
}
@Override
public void unregisterSensorListener(SensorEventListener listener) {
sensorManager.unregisterListener(listener);
}
@Override
public boolean isLocationProviderEnabled(String provider) {
return locationManager.isProviderEnabled(provider);
}
@Override
public void requestLocationUpdates(LocationListener listener) {
// Check if the provider exists.
LocationProvider gpsProvider = locationManager.getProvider(LocationManager.GPS_PROVIDER);
if (gpsProvider == null) {
listener.onProviderDisabled(LocationManager.GPS_PROVIDER);
locationManager.removeUpdates(listener);
return;
}
// Listen to GPS location.
String providerName = gpsProvider.getName();
Log.d(Constants.TAG, "TrackDataHub: Using location provider " + providerName);
locationManager.requestLocationUpdates(providerName,
0 /*minTime*/, 0 /*minDist*/, listener);
// Give an initial update on provider state.
if (locationManager.isProviderEnabled(providerName)) {
listener.onProviderEnabled(providerName);
} else {
listener.onProviderDisabled(providerName);
}
// Listen to network location
try {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, listener);
} catch (RuntimeException e) {
// If anything at all goes wrong with getting a cell location do not
// abort. Cell location is not essential to this app.
Log.w(Constants.TAG, "Could not register network location listener.", e);
}
}
@Override
public Location getLastKnownLocation() {
// TODO: Let's look at more advanced algorithms to determine the best
// current location.
Location loc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
final long now = System.currentTimeMillis();
if (loc == null || loc.getTime() < now - MAX_LOCATION_AGE_MS) {
// We don't have a recent GPS fix, just use cell towers if available
loc = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
int toastResId = R.string.my_location_approximate_location;
if (loc == null || loc.getTime() < now - MAX_NETWORK_AGE_MS) {
// We don't have a recent cell tower location, let the user know:
toastResId = R.string.my_location_no_location;
}
// Let the user know we have only an approximate location:
Toast.makeText(context, toastResId, Toast.LENGTH_LONG).show();
}
return loc;
}
@Override
public void removeLocationUpdates(LocationListener listener) {
locationManager.removeUpdates(listener);
}
}
@@ -0,0 +1,105 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
import java.util.EnumSet;
/**
* State for a registered {@link TrackDataListener}.
*
* @author Jimmy Shih
*/
public class ListenerState {
private TrackDataListener trackDataListener;
private EnumSet<TrackDataType> trackDataTypes;
private long lastTrackId;
private long lastPointId;
private int lastSamplingFrequency;
private int numberOfLoadedPoints;
public ListenerState(
TrackDataListener trackDataListener, EnumSet<TrackDataType> trackDataTypes) {
this.trackDataListener = trackDataListener;
this.trackDataTypes = trackDataTypes;
}
/**
* Gets the {@link TrackDataListener}.
*/
public TrackDataListener getTrackDataListener() {
return trackDataListener;
}
/**
* Gets the track data types.
*/
public EnumSet<TrackDataType> getTrackDataTypes() {
return trackDataTypes;
}
/**
* Gets the last track id.
*/
public long getLastTrackId() {
return lastTrackId;
}
/**
* Gets the last point id.
*/
public long getLastPointId() {
return lastPointId;
}
/**
* Gets the last sampling frequency.
*/
public int getLastSamplingFrequency() {
return lastSamplingFrequency;
}
/**
* Get the number of loaded points.
*/
public int getNumberOfLoadedPoints() {
return numberOfLoadedPoints;
}
/**
* Sets the listener state.
*
* @param trackId track id
* @param pointId point id
* @param samplingFrequency sampling frequency
* @param loadedPoints number of loaded points
*/
public void setState(long trackId, long pointId, int samplingFrequency, int loadedPoints) {
lastTrackId = trackId;
lastPointId = pointId;
lastSamplingFrequency = samplingFrequency;
numberOfLoadedPoints = loadedPoints;
}
/**
* Resets state.
*/
public void resetState() {
setState(0L, 0L, 0, 0);
}
}
@@ -16,7 +16,6 @@
package com.google.android.apps.mytracks.content;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R;
@@ -38,64 +37,58 @@ import android.text.TextUtils;
import android.util.Log;
/**
* A provider that handles recorded (GPS) tracks and their track points.
*
* A {@link ContentProvider} that handles access to track points, tracks, and
* waypoints tables.
*
* @author Leif Hendrik Wilden
*/
public class MyTracksProvider extends ContentProvider {
private static final String TAG = MyTracksProvider.class.getSimpleName();
private static final String DATABASE_NAME = "mytracks.db";
private static final int DATABASE_VERSION = 20;
private static final int TRACKPOINTS = 1;
private static final int TRACKPOINTS_ID = 2;
private static final int TRACKS = 3;
private static final int TRACKS_ID = 4;
private static final int WAYPOINTS = 5;
private static final int WAYPOINTS_ID = 6;
private static final String TAG = MyTracksProvider.class.getSimpleName();
/**
* Helper which creates or upgrades the database if necessary.
* Database helper for creating and upgrading the databasae.
*/
private static class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(TrackPointsColumns.CREATE_TABLE);
db.execSQL(TracksColumns.CREATE_TABLE);
db.execSQL(WaypointsColumns.CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion);
if (oldVersion < 17) {
// Delete the old data
Log.w(TAG, "Delete all old data");
Log.w(TAG, "Deleting all old data.");
db.execSQL("DROP TABLE IF EXISTS " + TrackPointsColumns.TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + TracksColumns.TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + WaypointsColumns.TABLE_NAME);
onCreate(db);
} else {
// Incremental updates go here. For each DB version, add a corresponding if clause.
// Track points sensor column
// Incremental upgrades. One if statement per DB version.
// Add track points SENSOR column
if (oldVersion <= 17) {
Log.w(TAG, "Upgrade DB: Adding sensor column.");
db.execSQL("ALTER TABLE " + TrackPointsColumns.TABLE_NAME + " ADD "
+ TrackPointsColumns.SENSOR + " BLOB");
}
// Tracks table id column
// Add tracks TABLEID column
if (oldVersion <= 18) {
Log.w(TAG, "Upgrade DB: Adding tableid column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.TABLEID
+ " STRING");
}
// Tracks table icon column
// Add tracks ICON column
if (oldVersion <= 19) {
Log.w(TAG, "Upgrade DB: Adding icon column.");
db.execSQL(
@@ -105,30 +98,32 @@ public class MyTracksProvider extends ContentProvider {
}
}
private final UriMatcher urlMatcher;
/**
* Types of url.
*
* @author Jimmy Shih
*/
private enum UrlType {
TRACKPOINTS, TRACKPOINTS_ID, TRACKS, TRACKS_ID, WAYPOINTS, WAYPOINTS_ID
}
private final UriMatcher uriMatcher;
private SQLiteDatabase db;
public MyTracksProvider() {
urlMatcher = new UriMatcher(UriMatcher.NO_MATCH);
urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY,
"trackpoints", TRACKPOINTS);
urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY,
"trackpoints/#", TRACKPOINTS_ID);
urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, "tracks", TRACKS);
urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, "tracks/#", TRACKS_ID);
urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, "waypoints", WAYPOINTS);
urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY,
"waypoints/#", WAYPOINTS_ID);
}
private boolean canAccess() {
if (Binder.getCallingPid() == Process.myPid()) {
return true;
} else {
return PreferencesUtils.getBoolean(
getContext(), R.string.allow_access_key, PreferencesUtils.ALLOW_ACCESS_DEFAULT);
}
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(MyTracksProviderUtils.AUTHORITY, TrackPointsColumns.TABLE_NAME,
UrlType.TRACKPOINTS.ordinal());
uriMatcher.addURI(MyTracksProviderUtils.AUTHORITY, TrackPointsColumns.TABLE_NAME + "/#",
UrlType.TRACKPOINTS_ID.ordinal());
uriMatcher.addURI(
MyTracksProviderUtils.AUTHORITY, TracksColumns.TABLE_NAME, UrlType.TRACKS.ordinal());
uriMatcher.addURI(MyTracksProviderUtils.AUTHORITY, TracksColumns.TABLE_NAME + "/#",
UrlType.TRACKS_ID.ordinal());
uriMatcher.addURI(
MyTracksProviderUtils.AUTHORITY, WaypointsColumns.TABLE_NAME, UrlType.WAYPOINTS.ordinal());
uriMatcher.addURI(MyTracksProviderUtils.AUTHORITY, WaypointsColumns.TABLE_NAME + "/#",
UrlType.WAYPOINTS_ID.ordinal());
}
@Override
@@ -136,11 +131,11 @@ public class MyTracksProvider extends ContentProvider {
if (!canAccess()) {
return false;
}
DatabaseHelper dbHelper = new DatabaseHelper(getContext());
DatabaseHelper databaseHelper = new DatabaseHelper(getContext());
try {
db = dbHelper.getWritableDatabase();
db = databaseHelper.getWritableDatabase();
} catch (SQLiteException e) {
Log.e(TAG, "Unable to open database for writing", e);
Log.e(TAG, "Unable to open database for writing.", e);
}
return db != null;
}
@@ -152,7 +147,7 @@ public class MyTracksProvider extends ContentProvider {
}
String table;
boolean shouldVacuum = false;
switch (urlMatcher.match(url)) {
switch (getUrlType(url)) {
case TRACKPOINTS:
table = TrackPointsColumns.TABLE_NAME;
break;
@@ -166,17 +161,23 @@ public class MyTracksProvider extends ContentProvider {
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
Log.w(MyTracksProvider.TAG, "provider delete in " + table + "!");
int count = db.delete(table, where, selectionArgs);
Log.w(MyTracksProvider.TAG, "Deleting table " + table);
int count;
try {
db.beginTransaction();
count = db.delete(table, where, selectionArgs);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
getContext().getContentResolver().notifyChange(url, null, true);
if (shouldVacuum) {
// If a potentially large amount of data was deleted, we want to reclaim its space.
Log.i(TAG, "Vacuuming the database");
// If a potentially large amount of data was deleted, reclaim its space.
Log.i(TAG, "Vacuuming the database.");
db.execSQL("VACUUM");
}
return count;
}
@@ -185,7 +186,7 @@ public class MyTracksProvider extends ContentProvider {
if (!canAccess()) {
return null;
}
switch (urlMatcher.match(url)) {
switch (getUrlType(url)) {
case TRACKPOINTS:
return TrackPointsColumns.CONTENT_TYPE;
case TRACKPOINTS_ID:
@@ -208,197 +209,240 @@ public class MyTracksProvider extends ContentProvider {
if (!canAccess()) {
return null;
}
Log.d(MyTracksProvider.TAG, "MyTracksProvider.insert");
ContentValues values;
if (initialValues != null) {
values = initialValues;
} else {
values = new ContentValues();
if (initialValues == null) {
initialValues = new ContentValues();
}
int urlMatchType = urlMatcher.match(url);
return insertType(url, urlMatchType, values);
}
private Uri insertType(Uri url, int urlMatchType, ContentValues values) {
switch (urlMatchType) {
case TRACKPOINTS:
return insertTrackPoint(url, values);
case TRACKS:
return insertTrack(url, values);
case WAYPOINTS:
return insertWaypoint(url, values);
default:
throw new IllegalArgumentException("Unknown URL " + url);
Uri result = null;
try {
db.beginTransaction();
result = insertContentValues(url, getUrlType(url), initialValues);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return result;
}
@Override
public int bulkInsert(Uri url, ContentValues[] valuesBulk) {
if (!canAccess()) {
return 0;
}
Log.d(MyTracksProvider.TAG, "MyTracksProvider.bulkInsert");
int numInserted = 0;
try {
// Use a transaction in order to make the insertions run as a single batch
db.beginTransaction();
int urlMatch = urlMatcher.match(url);
UrlType urlType = getUrlType(url);
for (numInserted = 0; numInserted < valuesBulk.length; numInserted++) {
ContentValues values = valuesBulk[numInserted];
if (values == null) { values = new ContentValues(); }
insertType(url, urlMatch, values);
ContentValues contentValues = valuesBulk[numInserted];
if (contentValues == null) {
contentValues = new ContentValues();
}
insertContentValues(url, urlType, contentValues);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return numInserted;
}
private Uri insertTrackPoint(Uri url, ContentValues values) {
boolean hasLat = values.containsKey(TrackPointsColumns.LATITUDE);
boolean hasLong = values.containsKey(TrackPointsColumns.LONGITUDE);
boolean hasTime = values.containsKey(TrackPointsColumns.TIME);
if (!hasLat || !hasLong || !hasTime) {
throw new IllegalArgumentException(
"Latitude, longitude, and time values are required.");
}
long rowId = db.insert(TrackPointsColumns.TABLE_NAME, TrackPointsColumns._ID, values);
if (rowId >= 0) {
Uri uri = ContentUris.appendId(
TrackPointsColumns.CONTENT_URI.buildUpon(), rowId).build();
getContext().getContentResolver().notifyChange(url, null, true);
return uri;
}
throw new SQLiteException("Failed to insert row into " + url);
}
private Uri insertTrack(Uri url, ContentValues values) {
boolean hasStartTime = values.containsKey(TracksColumns.STARTTIME);
boolean hasStartId = values.containsKey(TracksColumns.STARTID);
if (!hasStartTime || !hasStartId) {
throw new IllegalArgumentException(
"Both start time and start id values are required.");
}
long rowId = db.insert(TracksColumns.TABLE_NAME, TracksColumns._ID, values);
if (rowId > 0) {
Uri uri = ContentUris.appendId(
TracksColumns.CONTENT_URI.buildUpon(), rowId).build();
getContext().getContentResolver().notifyChange(url, null, true);
return uri;
}
throw new SQLException("Failed to insert row into " + url);
}
private Uri insertWaypoint(Uri url, ContentValues values) {
long rowId = db.insert(WaypointsColumns.TABLE_NAME, WaypointsColumns._ID, values);
if (rowId > 0) {
Uri uri = ContentUris.appendId(
WaypointsColumns.CONTENT_URI.buildUpon(), rowId).build();
getContext().getContentResolver().notifyChange(url, null, true);
return uri;
}
throw new SQLException("Failed to insert row into " + url);
}
@Override
public Cursor query(
Uri url, String[] projection, String selection, String[] selectionArgs,
String sort) {
Uri url, String[] projection, String selection, String[] selectionArgs, String sort) {
if (!canAccess()) {
return null;
}
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
int match = urlMatcher.match(url);
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
String sortOrder = null;
if (match == TRACKPOINTS) {
qb.setTables(TrackPointsColumns.TABLE_NAME);
if (sort != null) {
sortOrder = sort;
} else {
sortOrder = TrackPointsColumns.DEFAULT_SORT_ORDER;
}
} else if (match == TRACKPOINTS_ID) {
qb.setTables(TrackPointsColumns.TABLE_NAME);
qb.appendWhere("_id=" + url.getPathSegments().get(1));
} else if (match == TRACKS) {
qb.setTables(TracksColumns.TABLE_NAME);
if (sort != null) {
sortOrder = sort;
} else {
sortOrder = TracksColumns.DEFAULT_SORT_ORDER;
}
} else if (match == TRACKS_ID) {
qb.setTables(TracksColumns.TABLE_NAME);
qb.appendWhere("_id=" + url.getPathSegments().get(1));
} else if (match == WAYPOINTS) {
qb.setTables(WaypointsColumns.TABLE_NAME);
if (sort != null) {
sortOrder = sort;
} else {
sortOrder = WaypointsColumns.DEFAULT_SORT_ORDER;
}
} else if (match == WAYPOINTS_ID) {
qb.setTables(WaypointsColumns.TABLE_NAME);
qb.appendWhere("_id=" + url.getPathSegments().get(1));
} else {
throw new IllegalArgumentException("Unknown URL " + url);
switch (getUrlType(url)) {
case TRACKPOINTS:
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
sortOrder = sort != null ? sort : TrackPointsColumns.DEFAULT_SORT_ORDER;
break;
case TRACKPOINTS_ID:
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1));
break;
case TRACKS:
queryBuilder.setTables(TracksColumns.TABLE_NAME);
sortOrder = sort != null ? sort : TracksColumns.DEFAULT_SORT_ORDER;
break;
case TRACKS_ID:
queryBuilder.setTables(TracksColumns.TABLE_NAME);
queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1));
break;
case WAYPOINTS:
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
sortOrder = sort != null ? sort : WaypointsColumns.DEFAULT_SORT_ORDER;
break;
case WAYPOINTS_ID:
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1));
break;
default:
throw new IllegalArgumentException("Unknown url " + url);
}
Log.i(Constants.TAG, "Build query: "
+ qb.buildQuery(projection, selection, selectionArgs, null, null, sortOrder, null));
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null,
sortOrder);
c.setNotificationUri(getContext().getContentResolver(), url);
return c;
Cursor cursor = queryBuilder.query(
db, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), url);
return cursor;
}
@Override
public int update(Uri url, ContentValues values, String where,
String[] selectionArgs) {
public int update(Uri url, ContentValues values, String where, String[] selectionArgs) {
if (!canAccess()) {
return 0;
}
String table;
String whereClause;
switch (getUrlType(url)) {
case TRACKPOINTS:
table = TrackPointsColumns.TABLE_NAME;
whereClause = where;
break;
case TRACKPOINTS_ID:
table = TrackPointsColumns.TABLE_NAME;
whereClause = TrackPointsColumns._ID + "=" + url.getPathSegments().get(1);
if (!TextUtils.isEmpty(where)) {
whereClause += " AND (" + where + ")";
}
break;
case TRACKS:
table = TracksColumns.TABLE_NAME;
whereClause = where;
break;
case TRACKS_ID:
table = TracksColumns.TABLE_NAME;
whereClause = TracksColumns._ID + "=" + url.getPathSegments().get(1);
if (!TextUtils.isEmpty(where)) {
whereClause += " AND (" + where + ")";
}
break;
case WAYPOINTS:
table = WaypointsColumns.TABLE_NAME;
whereClause = where;
break;
case WAYPOINTS_ID:
table = WaypointsColumns.TABLE_NAME;
whereClause = WaypointsColumns._ID + "=" + url.getPathSegments().get(1);
if (!TextUtils.isEmpty(where)) {
whereClause += " AND (" + where + ")";
}
break;
default:
throw new IllegalArgumentException("Unknown url " + url);
}
int count;
int match = urlMatcher.match(url);
if (match == TRACKPOINTS) {
count = db.update(TrackPointsColumns.TABLE_NAME, values, where, selectionArgs);
} else if (match == TRACKPOINTS_ID) {
String segment = url.getPathSegments().get(1);
count = db.update(TrackPointsColumns.TABLE_NAME, values, "_id=" + segment
+ (!TextUtils.isEmpty(where)
? " AND (" + where + ')'
: ""),
selectionArgs);
} else if (match == TRACKS) {
count = db.update(TracksColumns.TABLE_NAME, values, where, selectionArgs);
} else if (match == TRACKS_ID) {
String segment = url.getPathSegments().get(1);
count = db.update(TracksColumns.TABLE_NAME, values, "_id=" + segment
+ (!TextUtils.isEmpty(where)
? " AND (" + where + ')'
: ""),
selectionArgs);
} else if (match == WAYPOINTS) {
count = db.update(WaypointsColumns.TABLE_NAME, values, where, selectionArgs);
} else if (match == WAYPOINTS_ID) {
String segment = url.getPathSegments().get(1);
count = db.update(WaypointsColumns.TABLE_NAME, values, "_id=" + segment
+ (!TextUtils.isEmpty(where)
? " AND (" + where + ')'
: ""),
selectionArgs);
} else {
throw new IllegalArgumentException("Unknown URL " + url);
try {
db.beginTransaction();
count = db.update(table, values, whereClause, selectionArgs);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
getContext().getContentResolver().notifyChange(url, null, true);
return count;
}
/**
* Returns true if the caller can access the content provider.
*/
private boolean canAccess() {
if (Binder.getCallingPid() == Process.myPid()) {
return true;
} else {
return PreferencesUtils.getBoolean(
getContext(), R.string.allow_access_key, PreferencesUtils.ALLOW_ACCESS_DEFAULT);
}
}
/**
* Gets the {@link UrlType} for a url.
*
* @param url the url
*/
private UrlType getUrlType(Uri url) {
return UrlType.values()[uriMatcher.match(url)];
}
/**
* Inserts a content based on the url type.
*
* @param url the content url
* @param urlType the url type
* @param contentValues the content values
*/
private Uri insertContentValues(Uri url, UrlType urlType, ContentValues contentValues) {
switch (urlType) {
case TRACKPOINTS:
return insertTrackPoint(url, contentValues);
case TRACKS:
return insertTrack(url, contentValues);
case WAYPOINTS:
return insertWaypoint(url, contentValues);
default:
throw new IllegalArgumentException("Unknown url " + url);
}
}
/**
* Inserts a track point.
*
* @param url the content url
* @param values the content values
*/
private Uri insertTrackPoint(Uri url, ContentValues values) {
boolean hasLatitude = values.containsKey(TrackPointsColumns.LATITUDE);
boolean hasLongitude = values.containsKey(TrackPointsColumns.LONGITUDE);
boolean hasTime = values.containsKey(TrackPointsColumns.TIME);
if (!hasLatitude || !hasLongitude || !hasTime) {
throw new IllegalArgumentException("Latitude, longitude, and time values are required.");
}
long rowId = db.insert(TrackPointsColumns.TABLE_NAME, TrackPointsColumns._ID, values);
if (rowId >= 0) {
Uri uri = ContentUris.appendId(TrackPointsColumns.CONTENT_URI.buildUpon(), rowId).build();
getContext().getContentResolver().notifyChange(url, null, true);
return uri;
}
throw new SQLiteException("Failed to insert a track point " + url);
}
/**
* Inserts a track.
*
* @param url the content url
* @param contentValues the content values
*/
private Uri insertTrack(Uri url, ContentValues contentValues) {
boolean hasStartTime = contentValues.containsKey(TracksColumns.STARTTIME);
boolean hasStartId = contentValues.containsKey(TracksColumns.STARTID);
if (!hasStartTime || !hasStartId) {
throw new IllegalArgumentException("Both start time and start id values are required.");
}
long rowId = db.insert(TracksColumns.TABLE_NAME, TracksColumns._ID, contentValues);
if (rowId >= 0) {
Uri uri = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), rowId).build();
getContext().getContentResolver().notifyChange(url, null, true);
return uri;
}
throw new SQLException("Failed to insert a track " + url);
}
/**
* Inserts a waypoint.
*
* @param url the content url
* @param contentValues the content values
*/
private Uri insertWaypoint(Uri url, ContentValues contentValues) {
long rowId = db.insert(WaypointsColumns.TABLE_NAME, WaypointsColumns._ID, contentValues);
if (rowId >= 0) {
Uri uri = ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), rowId).build();
getContext().getContentResolver().notifyChange(url, null, true);
return uri;
}
throw new SQLException("Failed to insert a waypoint " + url);
}
}
@@ -21,18 +21,14 @@ import static com.google.android.apps.mytracks.Constants.MAX_NETWORK_AGE_MS;
import static com.google.android.apps.mytracks.Constants.TAG;
import static com.google.android.apps.mytracks.Constants.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;
import com.google.android.apps.mytracks.content.TrackDataListeners.ListenerRegistration;
import com.google.android.apps.mytracks.util.LocationUtils;
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;
@@ -60,54 +56,23 @@ public class TrackDataHub {
// Overridable constants
private final int targetNumPoints;
/** Types of data that we can expose. */
public static enum ListenerDataType {
/** Listen to when the selected track changes. */
SELECTED_TRACK_CHANGED,
/** Listen to when the tracks change. */
TRACK_UPDATES,
/** Listen to when the waypoints change. */
WAYPOINT_UPDATES,
/** Listen to when the current track points change. */
POINT_UPDATES,
/**
* Listen to sampled-out points.
* Listening to this without listening to {@link #POINT_UPDATES}
* makes no sense and may yield unexpected results.
*/
SAMPLED_OUT_POINT_UPDATES,
/** Listen to updates to the current location. */
LOCATION_UPDATES,
/** Listen to updates to the current heading. */
COMPASS_UPDATES,
/** Listens to changes in display preferences. */
DISPLAY_PREFERENCES;
}
/** Listener which receives events from the system. */
private class HubDataSourceListener implements DataSourceListener {
@Override
public void notifyTrackUpdated() {
TrackDataHub.this.notifyTrackUpdated(getListenersFor(ListenerDataType.TRACK_UPDATES));
public void notifyTracksTableUpdated() {
TrackDataHub.this.notifyTrackUpdated(getListenersFor(TrackDataType.TRACKS_TABLE));
}
@Override
public void notifyWaypointUpdated() {
TrackDataHub.this.notifyWaypointUpdated(getListenersFor(ListenerDataType.WAYPOINT_UPDATES));
public void notifyWaypointsTableUpdated() {
TrackDataHub.this.notifyWaypointUpdated(getListenersFor(TrackDataType.WAYPOINTS_TABLE));
}
@Override
public void notifyPointsUpdated() {
public void notifyTrackPointsTableUpdated() {
TrackDataHub.this.notifyPointsUpdated(true, 0, 0,
getListenersFor(ListenerDataType.POINT_UPDATES),
getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES));
getListenersFor(TrackDataType.TRACK_POINTS_TABLE),
getListenersFor(TrackDataType.SAMPLED_OUT_TRACK_POINTS));
}
@Override
@@ -130,21 +95,20 @@ public class TrackDataHub {
@Override
public void notifyLocationChanged(Location loc) {
TrackDataHub.this.notifyLocationChanged(loc,
getListenersFor(ListenerDataType.LOCATION_UPDATES));
getListenersFor(TrackDataType.LOCATION));
}
@Override
public void notifyHeadingChanged(float heading) {
lastSeenMagneticHeading = heading;
maybeUpdateDeclination();
TrackDataHub.this.notifyHeadingChanged(getListenersFor(ListenerDataType.COMPASS_UPDATES));
TrackDataHub.this.notifyHeadingChanged(getListenersFor(TrackDataType.COMPASS));
}
}
// 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.
@@ -152,12 +116,9 @@ public class TrackDataHub {
private Handler listenerHandler;
/** Manager for external listeners (those from activities). */
private final TrackDataListeners dataListeners;
private final TrackDataManager trackDataManager;
/** Wrapper for interacting with system data managers. */
private DataSourcesWrapper dataSources;
/** Manager for system data listener registrations. */
private DataSource dataSource;
private DataSourceManager dataSourceManager;
/** Condensed listener for system data listener events. */
@@ -193,24 +154,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 TrackDataManager(), providerUtils, TARGET_DISPLAYED_TRACK_POINTS);
}
/**
* Injection constructor.
*/
// @VisibleForTesting
TrackDataHub(Context ctx, TrackDataListeners listeners, SharedPreferences preferences,
MyTracksProviderUtils providerUtils, int targetNumPoints) {
TrackDataHub(Context ctx, TrackDataManager trackDataManager, MyTracksProviderUtils providerUtils,
int targetNumPoints) {
this.context = ctx;
this.dataListeners = listeners;
this.preferences = preferences;
this.trackDataManager = trackDataManager;
this.providerUtils = providerUtils;
this.targetNumPoints = targetNumPoints;
this.locationFactory = new DoubleBufferedLocationFactory();
@@ -233,12 +189,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 +202,8 @@ public class TrackDataHub {
}
// @VisibleForTesting
protected DataSourcesWrapper newDataSources() {
return new DataSourcesWrapperImpl(context, preferences);
protected DataSource newDataSource() {
return new DataSource(context);
}
/**
@@ -267,7 +223,7 @@ public class TrackDataHub {
started = false;
dataSources = null;
dataSource = null;
dataSourceManager = null;
listenerHandlerThread = null;
listenerHandler = null;
@@ -343,9 +299,9 @@ 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));
notifyLocationChanged(loc, getListenersFor(TrackDataType.LOCATION));
}
}
@@ -415,30 +371,31 @@ public class TrackDataHub {
}
public void registerTrackDataListener(
TrackDataListener listener, EnumSet<ListenerDataType> dataTypes) {
synchronized (dataListeners) {
ListenerRegistration registration =
dataListeners.registerTrackDataListener(listener, dataTypes);
TrackDataListener listener, EnumSet<TrackDataType> dataTypes) {
synchronized (trackDataManager) {
ListenerState listenerState = trackDataManager.registerListener(listener, dataTypes);
// Don't load any data or start internal listeners if start() hasn't been
// called. When it is called, we'll do both things.
if (!isStarted()) return;
if (!isStarted()) {
return;
}
loadNewDataForListener(registration);
loadNewDataForListener(listenerState);
dataSourceManager.updateAllListeners(getNeededListenerTypes());
dataSourceManager.updateListeners(getNeededListenerTypes());
}
}
public void unregisterTrackDataListener(TrackDataListener listener) {
synchronized (dataListeners) {
dataListeners.unregisterTrackDataListener(listener);
synchronized (trackDataManager) {
trackDataManager.unregisterListener(listener);
// Don't load any data or start internal listeners if start() hasn't been
// called. When it is called, we'll do both things.
if (!isStarted()) return;
dataSourceManager.updateAllListeners(getNeededListenerTypes());
dataSourceManager.updateListeners(getNeededListenerTypes());
}
}
@@ -446,33 +403,32 @@ public class TrackDataHub {
* Reloads all track data received so far into the specified listeners.
*/
public void reloadDataForListener(TrackDataListener listener) {
ListenerRegistration registration;
synchronized (dataListeners) {
registration = dataListeners.getRegistration(listener);
registration.resetState();
loadNewDataForListener(registration);
synchronized (trackDataManager) {
ListenerState listenerState = trackDataManager.getListenerState(listener);
listenerState.resetState();
loadNewDataForListener(listenerState);
}
}
/**
* Reloads all track data received so far into the specified listeners.
*
* Assumes it's called from a block that synchronizes on {@link #dataListeners}.
* Assumes it's called from a block that synchronizes on {@link #trackDataManager}.
*/
private void loadNewDataForListener(final ListenerRegistration registration) {
private void loadNewDataForListener(final ListenerState listenerState) {
if (!isStarted()) {
Log.w(TAG, "Not started, not reloading");
return;
}
if (registration == null) {
Log.w(TAG, "Not reloading for null registration");
if (listenerState == null) {
Log.w(TAG, "Not reloading for null listener state");
return;
}
// If a listener happens to be added after this method but before the Runnable below is
// executed, it will have triggered a separate call to load data only up to the point this
// listener got to. This is ensured by being synchronized on listeners.
final boolean isOnlyListener = (dataListeners.getNumListeners() == 1);
final boolean isOnlyListener = (trackDataManager.getNumberOfListeners() == 1);
runInListenerThread(new Runnable() {
@SuppressWarnings("unchecked")
@@ -480,30 +436,31 @@ public class TrackDataHub {
public void run() {
// Reload everything if either it's a different track, or the track has been resampled
// (this also covers the case of a new registration).
boolean reloadAll = registration.lastTrackId != selectedTrackId ||
registration.lastSamplingFrequency != lastSamplingFrequency;
Log.d(TAG, "Doing a " + (reloadAll ? "full" : "partial") + " reload for " + registration);
boolean reloadAll = listenerState.getLastTrackId() != selectedTrackId ||
listenerState.getLastSamplingFrequency() != lastSamplingFrequency;
Log.d(TAG, "Doing a " + (reloadAll ? "full" : "partial") + " reload for " + listenerState);
TrackDataListener listener = registration.listener;
TrackDataListener listener = listenerState.getTrackDataListener();
Set<TrackDataListener> listenerSet = Collections.singleton(listener);
if (registration.isInterestedIn(ListenerDataType.DISPLAY_PREFERENCES)) {
EnumSet<TrackDataType> trackDataTypes = listenerState.getTrackDataTypes();
if (trackDataTypes.contains(TrackDataType.PREFERENCE)) {
reloadAll |= listener.onUnitsChanged(metricUnits);
reloadAll |= listener.onReportSpeedChanged(reportSpeed);
}
if (reloadAll && registration.isInterestedIn(ListenerDataType.SELECTED_TRACK_CHANGED)) {
if (reloadAll && trackDataTypes.contains(TrackDataType.SELECTED_TRACK)) {
notifySelectedTrackChanged(selectedTrackId, listenerSet);
}
if (registration.isInterestedIn(ListenerDataType.TRACK_UPDATES)) {
if (trackDataTypes.contains(TrackDataType.TRACKS_TABLE)) {
notifyTrackUpdated(listenerSet);
}
boolean interestedInPoints =
registration.isInterestedIn(ListenerDataType.POINT_UPDATES);
boolean interestedInSampledOutPoints =
registration.isInterestedIn(ListenerDataType.SAMPLED_OUT_POINT_UPDATES);
boolean interestedInPoints = trackDataTypes.contains(TrackDataType.TRACK_POINTS_TABLE);
boolean interestedInSampledOutPoints = trackDataTypes.contains(
TrackDataType.SAMPLED_OUT_TRACK_POINTS);
if (interestedInPoints || interestedInSampledOutPoints) {
long minPointId = 0;
int previousNumPoints = 0;
@@ -513,8 +470,8 @@ public class TrackDataHub {
notifyPointsCleared(listenerSet);
} else {
// Send only new points
minPointId = registration.lastPointId + 1;
previousNumPoints = registration.numLoadedPoints;
minPointId = listenerState.getLastPointId() + 1;
previousNumPoints = listenerState.getNumberOfLoadedPoints();
}
// If this is the only listener we have registered, keep the state that we serve to it as
@@ -530,11 +487,11 @@ public class TrackDataHub {
interestedInSampledOutPoints ? listenerSet : Collections.EMPTY_SET);
}
if (registration.isInterestedIn(ListenerDataType.WAYPOINT_UPDATES)) {
if (trackDataTypes.contains(TrackDataType.WAYPOINTS_TABLE)) {
notifyWaypointUpdated(listenerSet);
}
if (registration.isInterestedIn(ListenerDataType.LOCATION_UPDATES)) {
if (trackDataTypes.contains(TrackDataType.LOCATION)) {
if (lastSeenLocation != null) {
notifyLocationChanged(lastSeenLocation, true, listenerSet);
} else {
@@ -542,7 +499,7 @@ public class TrackDataHub {
}
}
if (registration.isInterestedIn(ListenerDataType.COMPASS_UPDATES)) {
if (trackDataTypes.contains(TrackDataType.COMPASS)) {
notifyHeadingChanged(listenerSet);
}
}
@@ -557,8 +514,8 @@ public class TrackDataHub {
Log.w(TAG, "Not started, not reloading");
return;
}
synchronized (dataListeners) {
if (!dataListeners.hasListeners()) {
synchronized (trackDataManager) {
if (trackDataManager.getNumberOfListeners() == 0) {
Log.d(TAG, "No listeners, not reloading");
return;
}
@@ -569,33 +526,33 @@ public class TrackDataHub {
public void run() {
// Ignore the return values here, we're already sending the full data set anyway
for (TrackDataListener listener :
getListenersFor(ListenerDataType.DISPLAY_PREFERENCES)) {
getListenersFor(TrackDataType.PREFERENCE)) {
listener.onUnitsChanged(metricUnits);
listener.onReportSpeedChanged(reportSpeed);
}
notifySelectedTrackChanged(selectedTrackId,
getListenersFor(ListenerDataType.SELECTED_TRACK_CHANGED));
getListenersFor(TrackDataType.SELECTED_TRACK));
notifyTrackUpdated(getListenersFor(ListenerDataType.TRACK_UPDATES));
notifyTrackUpdated(getListenersFor(TrackDataType.TRACKS_TABLE));
Set<TrackDataListener> pointListeners =
getListenersFor(ListenerDataType.POINT_UPDATES);
getListenersFor(TrackDataType.TRACK_POINTS_TABLE);
Set<TrackDataListener> sampledOutPointListeners =
getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES);
getListenersFor(TrackDataType.SAMPLED_OUT_TRACK_POINTS);
notifyPointsCleared(pointListeners);
notifyPointsUpdated(true, 0, 0, pointListeners, sampledOutPointListeners);
notifyWaypointUpdated(getListenersFor(ListenerDataType.WAYPOINT_UPDATES));
notifyWaypointUpdated(getListenersFor(TrackDataType.WAYPOINTS_TABLE));
if (lastSeenLocation != null) {
notifyLocationChanged(lastSeenLocation, true,
getListenersFor(ListenerDataType.LOCATION_UPDATES));
getListenersFor(TrackDataType.LOCATION));
} else {
notifyFixType();
}
notifyHeadingChanged(getListenersFor(ListenerDataType.COMPASS_UPDATES));
notifyHeadingChanged(getListenersFor(TrackDataType.COMPASS));
}
});
}
@@ -630,12 +587,12 @@ public class TrackDataHub {
@Override
public void run() {
Set<TrackDataListener> displayListeners =
getListenersFor(ListenerDataType.DISPLAY_PREFERENCES);
getListenersFor(TrackDataType.PREFERENCE);
for (TrackDataListener listener : displayListeners) {
// TODO: Do the reloading just once for all interested listeners
if (listener.onReportSpeedChanged(reportSpeed)) {
synchronized (dataListeners) {
synchronized (trackDataManager) {
reloadDataForListener(listener);
}
}
@@ -651,11 +608,11 @@ public class TrackDataHub {
runInListenerThread(new Runnable() {
@Override
public void run() {
Set<TrackDataListener> displayListeners = getListenersFor(ListenerDataType.DISPLAY_PREFERENCES);
Set<TrackDataListener> displayListeners = getListenersFor(TrackDataType.PREFERENCE);
for (TrackDataListener listener : displayListeners) {
if (listener.onUnitsChanged(metricUnits)) {
synchronized (dataListeners) {
synchronized (trackDataManager) {
reloadDataForListener(listener);
}
}
@@ -683,7 +640,7 @@ public class TrackDataHub {
// Notify to everyone.
Log.d(TAG, "Notifying fix type: " + state);
for (TrackDataListener listener :
getListenersFor(ListenerDataType.LOCATION_UPDATES)) {
getListenersFor(TrackDataType.LOCATION)) {
listener.onProviderStateChange(state);
}
}
@@ -945,9 +902,9 @@ public class TrackDataHub {
Log.i(TAG, "Resampling point set after " + numLoadedPoints + " points.");
resetState();
synchronized (dataListeners) {
sampledListeners = getListenersFor(ListenerDataType.POINT_UPDATES);
sampledOutListeners = getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES);
synchronized (trackDataManager) {
sampledListeners = getListenersFor(TrackDataType.TRACK_POINTS_TABLE);
sampledOutListeners = getListenersFor(TrackDataType.SAMPLED_OUT_TRACK_POINTS);
}
maxPointId = -1;
minPointId = 0;
@@ -1030,12 +987,10 @@ public class TrackDataHub {
listener.onNewTrackPointsDone();
// Update the listener state
ListenerRegistration registration = dataListeners.getRegistration(listener);
if (registration != null) {
registration.lastTrackId = currentSelectedTrackId;
registration.lastPointId = localLastSeenLocationId;
registration.lastSamplingFrequency = pointSamplingFrequency;
registration.numLoadedPoints = localNumLoadedPoints;
ListenerState listenerState = trackDataManager.getListenerState(listener);
if (listenerState != null) {
listenerState.setState(currentSelectedTrackId, localLastSeenLocationId,
pointSamplingFrequency, localNumLoadedPoints);
}
}
}
@@ -1089,17 +1044,17 @@ public class TrackDataHub {
listenerHandler.post(runnable);
}
private Set<TrackDataListener> getListenersFor(ListenerDataType type) {
synchronized (dataListeners) {
return dataListeners.getListenersFor(type);
private Set<TrackDataListener> getListenersFor(TrackDataType type) {
synchronized (trackDataManager) {
return trackDataManager.getListeners(type);
}
}
private EnumSet<ListenerDataType> getNeededListenerTypes() {
EnumSet<ListenerDataType> neededTypes = dataListeners.getAllRegisteredTypes();
private EnumSet<TrackDataType> getNeededListenerTypes() {
EnumSet<TrackDataType> neededTypes = trackDataManager.getRegisteredTrackDataTypes();
// We always want preference updates.
neededTypes.add(ListenerDataType.DISPLAY_PREFERENCES);
neededTypes.add(TrackDataType.PREFERENCE);
return neededTypes;
}
@@ -1,159 +0,0 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import android.util.Log;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* Manager for the external data listeners and their listening types.
*
* @author Rodrigo Damazio
*/
class TrackDataListeners {
/** Internal representation of a listener's registration. */
static class ListenerRegistration {
final TrackDataListener listener;
final EnumSet<ListenerDataType> types;
// State that was last notified to the listener, for resuming after a pause.
long lastTrackId;
long lastPointId;
int lastSamplingFrequency;
int numLoadedPoints;
public ListenerRegistration(TrackDataListener listener,
EnumSet<ListenerDataType> types) {
this.listener = listener;
this.types = types;
}
public boolean isInterestedIn(ListenerDataType type) {
return types.contains(type);
}
public void resetState() {
lastTrackId = 0L;
lastPointId = 0L;
lastSamplingFrequency = 0;
numLoadedPoints = 0;
}
@Override
public String toString() {
return "ListenerRegistration [listener=" + listener + ", types=" + types
+ ", lastTrackId=" + lastTrackId + ", lastPointId=" + lastPointId
+ ", lastSamplingFrequency=" + lastSamplingFrequency
+ ", numLoadedPoints=" + numLoadedPoints + "]";
}
}
/** Map of external listener to its registration details. */
private final Map<TrackDataListener, ListenerRegistration> registeredListeners =
new HashMap<TrackDataListener, ListenerRegistration>();
/** Map of data type to external listeners interested in it. */
private final Map<ListenerDataType, Set<TrackDataListener>> listenerSetsPerType =
new EnumMap<ListenerDataType, Set<TrackDataListener>>(ListenerDataType.class);
public TrackDataListeners() {
// Create sets for all data types at startup.
for (ListenerDataType type : ListenerDataType.values()) {
listenerSetsPerType.put(type, new LinkedHashSet<TrackDataListener>());
}
}
/**
* Registers a listener to send data to.
* It is ok to call this method before {@link TrackDataHub#start}, and in that case
* the data will only be passed to listeners when {@link TrackDataHub#start} is called.
*
* @param listener the listener to register
* @param dataTypes the type of data that the listener is interested in
*/
public ListenerRegistration registerTrackDataListener(final TrackDataListener listener, EnumSet<ListenerDataType> dataTypes) {
Log.d(TAG, "Registered track data listener: " + listener);
if (registeredListeners.containsKey(listener)) {
throw new IllegalStateException("Listener already registered");
}
ListenerRegistration registration = new ListenerRegistration(listener, dataTypes);
registeredListeners.put(listener, registration);
for (ListenerDataType type : dataTypes) {
// This is guaranteed not to be null.
Set<TrackDataListener> typeSet = listenerSetsPerType.get(type);
typeSet.add(listener);
}
return registration;
}
/**
* Unregisters a listener to send data to.
*
* @param listener the listener to unregister
*/
public void unregisterTrackDataListener(TrackDataListener listener) {
Log.d(TAG, "Unregistered track data listener: " + listener);
// Remove and keep the corresponding registration.
ListenerRegistration match = registeredListeners.remove(listener);
if (match == null) {
Log.w(TAG, "Tried to unregister listener which is not registered.");
return;
}
// Remove it from the per-type sets
for (ListenerDataType type : match.types) {
listenerSetsPerType.get(type).remove(listener);
}
}
public ListenerRegistration getRegistration(TrackDataListener listener) {
return registeredListeners.get(listener);
}
public Set<TrackDataListener> getListenersFor(ListenerDataType type) {
return listenerSetsPerType.get(type);
}
public EnumSet<ListenerDataType> getAllRegisteredTypes() {
EnumSet<ListenerDataType> listeners = EnumSet.noneOf(ListenerDataType.class);
for (ListenerRegistration registration : this.registeredListeners.values()) {
listeners.addAll(registration.types);
}
return listeners;
}
public boolean hasListeners() {
return !registeredListeners.isEmpty();
}
public int getNumListeners() {
return registeredListeners.size();
}
}
@@ -0,0 +1,127 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.Constants.TAG;
import android.util.Log;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* Manages register/unregister {@link TrackDataListener} and keeping the state
* for each registered listener.
*
* @author Rodrigo Damazio
*/
public class TrackDataManager {
// Map of listener to its state
private final Map<TrackDataListener, ListenerState>
listenerToStateMap = new HashMap<TrackDataListener, ListenerState>();
// Map of track data type to listeners
private final Map<TrackDataType, Set<TrackDataListener>>
typeToListenersMap = new EnumMap<TrackDataType, Set<TrackDataListener>>(TrackDataType.class);
public TrackDataManager() {
for (TrackDataType trackDataType : TrackDataType.values()) {
typeToListenersMap.put(trackDataType, new LinkedHashSet<TrackDataListener>());
}
}
/**
* Registers a listener.
*
* @param listener the listener
* @param trackDataTypes the track data types the listener is interested
*/
public ListenerState registerListener(
TrackDataListener listener, EnumSet<TrackDataType> trackDataTypes) {
if (listenerToStateMap.containsKey(listener)) {
throw new IllegalStateException("Listener is already registered");
}
ListenerState listenerState = new ListenerState(listener, trackDataTypes);
listenerToStateMap.put(listener, listenerState);
for (TrackDataType trackDataType : trackDataTypes) {
typeToListenersMap.get(trackDataType).add(listener);
}
return listenerState;
}
/**
* Unregisters a listener.
*
* @param listener the listener
*/
public void unregisterListener(TrackDataListener listener) {
ListenerState removed = listenerToStateMap.remove(listener);
if (removed == null) {
Log.w(TAG, "Tried to unregister a listener that is not registered.");
return;
}
// Remove the listener from the typeToListenersMap
for (TrackDataType trackDataType : removed.getTrackDataTypes()) {
typeToListenersMap.get(trackDataType).remove(listener);
}
}
/**
* Gets the number of {@link TrackDataListener}.
*/
public int getNumberOfListeners() {
return listenerToStateMap.size();
}
/**
* Gets the track listener state.
*
* @param listener the listener
*/
public ListenerState getListenerState(TrackDataListener listener) {
return listenerToStateMap.get(listener);
}
/**
* Gets the listeners for a {@link TrackDataType}.
*
* @param type the type
*/
public Set<TrackDataListener> getListeners(TrackDataType type) {
return typeToListenersMap.get(type);
}
/**
* Gets all the registered {@link TrackDataType}.
*/
public EnumSet<TrackDataType> getRegisteredTrackDataTypes() {
EnumSet<TrackDataType> types = EnumSet.noneOf(TrackDataType.class);
for (ListenerState registration : this.listenerToStateMap.values()) {
types.addAll(registration.getTrackDataTypes());
}
return types;
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
/**
* Types of track data.
*
* @author Jimmy Shih
*/
public enum TrackDataType {
SELECTED_TRACK, // selected track changes
TRACKS_TABLE, // tracks table changes
WAYPOINTS_TABLE, // waypoints table changes
TRACK_POINTS_TABLE, // track points table changes
SAMPLED_OUT_TRACK_POINTS, // sampled-out track points changes
LOCATION, // location changes
COMPASS, // compass changes
PREFERENCE; // preference changes
}
@@ -19,12 +19,12 @@ package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.ChartView;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.MyTracksApplication;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
@@ -360,12 +360,12 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private synchronized void resumeTrackDataHub() {
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
ListenerDataType.WAYPOINT_UPDATES,
ListenerDataType.POINT_UPDATES,
ListenerDataType.SAMPLED_OUT_POINT_UPDATES,
ListenerDataType.DISPLAY_PREFERENCES));
TrackDataType.SELECTED_TRACK,
TrackDataType.TRACKS_TABLE,
TrackDataType.WAYPOINTS_TABLE,
TrackDataType.TRACK_POINTS_TABLE,
TrackDataType.SAMPLED_OUT_TRACK_POINTS,
TrackDataType.PREFERENCE));
}
/**
@@ -19,11 +19,11 @@ package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.MapOverlay;
import com.google.android.apps.mytracks.MyTracksApplication;
import com.google.android.apps.mytracks.TrackDetailActivity;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics;
@@ -420,11 +420,11 @@ public class MapFragment extends Fragment
private synchronized void resumeTrackDataHub() {
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.WAYPOINT_UPDATES,
ListenerDataType.POINT_UPDATES,
ListenerDataType.LOCATION_UPDATES,
ListenerDataType.COMPASS_UPDATES));
TrackDataType.SELECTED_TRACK,
TrackDataType.WAYPOINTS_TABLE,
TrackDataType.TRACK_POINTS_TABLE,
TrackDataType.LOCATION,
TrackDataType.COMPASS));
}
/**
@@ -17,9 +17,9 @@
package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.MyTracksApplication;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics;
@@ -257,10 +257,10 @@ public class StatsFragment extends Fragment implements TrackDataListener {
private synchronized void resumeTrackDataHub() {
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
ListenerDataType.LOCATION_UPDATES,
ListenerDataType.DISPLAY_PREFERENCES));
TrackDataType.SELECTED_TRACK,
TrackDataType.TRACKS_TABLE,
TrackDataType.LOCATION,
TrackDataType.PREFERENCE));
}
/**
@@ -27,11 +27,9 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.content.WaypointCreationRequest.WaypointType;
import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.services.sensors.SensorManager;
import com.google.android.apps.mytracks.services.sensors.SensorManagerFactory;
import com.google.android.apps.mytracks.services.tasks.PeriodicTaskExecutor;
@@ -49,7 +47,6 @@ import com.google.common.annotations.VisibleForTesting;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
@@ -836,35 +833,17 @@ public class TrackRecordingService extends Service {
int pointId = Integer.parseInt(pointUri.getLastPathSegment());
// Update the current track:
if (lastRecordedLocation != null
&& lastRecordedLocation.getLatitude() < 90) {
ContentValues values = new ContentValues();
TripStatistics stats = statsBuilder.getStatistics();
if (lastRecordedLocation != null && lastRecordedLocation.getLatitude() < 90) {
TripStatistics tripStatistics = statsBuilder.getStatistics();
tripStatistics.setStopTime(System.currentTimeMillis());
if (recordingTrack.getStartId() < 0) {
values.put(TracksColumns.STARTID, pointId);
recordingTrack.setStartId(pointId);
}
values.put(TracksColumns.STOPID, pointId);
values.put(TracksColumns.STOPTIME, System.currentTimeMillis());
values.put(TracksColumns.NUMPOINTS,
recordingTrack.getNumberOfPoints() + 1);
values.put(TracksColumns.MINLAT, stats.getBottom());
values.put(TracksColumns.MAXLAT, stats.getTop());
values.put(TracksColumns.MINLON, stats.getLeft());
values.put(TracksColumns.MAXLON, stats.getRight());
values.put(TracksColumns.TOTALDISTANCE, stats.getTotalDistance());
values.put(TracksColumns.TOTALTIME, stats.getTotalTime());
values.put(TracksColumns.MOVINGTIME, stats.getMovingTime());
values.put(TracksColumns.AVGSPEED, stats.getAverageSpeed());
values.put(TracksColumns.AVGMOVINGSPEED, stats.getAverageMovingSpeed());
values.put(TracksColumns.MAXSPEED, stats.getMaxSpeed());
values.put(TracksColumns.MINELEVATION, stats.getMinElevation());
values.put(TracksColumns.MAXELEVATION, stats.getMaxElevation());
values.put(TracksColumns.ELEVATIONGAIN, stats.getTotalElevationGain());
values.put(TracksColumns.MINGRADE, stats.getMinGrade());
values.put(TracksColumns.MAXGRADE, stats.getMaxGrade());
getContentResolver().update(TracksColumns.CONTENT_URI,
values, "_id=" + recordingTrack.getId(), null);
recordingTrack.setStopId(pointId);
recordingTrack.setNumberOfPoints(recordingTrack.getNumberOfPoints() + 1);
recordingTrack.setTripStatistics(tripStatistics);
providerUtils.updateTrack(recordingTrack);
updateCurrentWaypoint();
}
} catch (SQLiteException e) {
@@ -882,30 +861,14 @@ public class TrackRecordingService extends Service {
private void updateCurrentWaypoint() {
if (currentWaypointId >= 0) {
ContentValues values = new ContentValues();
TripStatistics waypointStats = waypointStatsBuilder.getStatistics();
values.put(WaypointsColumns.STARTTIME, waypointStats.getStartTime());
values.put(WaypointsColumns.LENGTH, length);
values.put(WaypointsColumns.DURATION, System.currentTimeMillis()
- statsBuilder.getStatistics().getStartTime());
values.put(WaypointsColumns.TOTALDISTANCE,
waypointStats.getTotalDistance());
values.put(WaypointsColumns.TOTALTIME, waypointStats.getTotalTime());
values.put(WaypointsColumns.MOVINGTIME, waypointStats.getMovingTime());
values.put(WaypointsColumns.AVGSPEED, waypointStats.getAverageSpeed());
values.put(WaypointsColumns.AVGMOVINGSPEED,
waypointStats.getAverageMovingSpeed());
values.put(WaypointsColumns.MAXSPEED, waypointStats.getMaxSpeed());
values.put(WaypointsColumns.MINELEVATION,
waypointStats.getMinElevation());
values.put(WaypointsColumns.MAXELEVATION,
waypointStats.getMaxElevation());
values.put(WaypointsColumns.ELEVATIONGAIN,
waypointStats.getTotalElevationGain());
values.put(WaypointsColumns.MINGRADE, waypointStats.getMinGrade());
values.put(WaypointsColumns.MAXGRADE, waypointStats.getMaxGrade());
getContentResolver().update(WaypointsColumns.CONTENT_URI,
values, "_id=" + currentWaypointId, null);
Waypoint waypoint = providerUtils.getWaypoint(currentWaypointId);
if (waypoint != null) {
waypoint.setLength(length);
waypoint.setDuration(
System.currentTimeMillis() - statsBuilder.getStatistics().getStartTime());
waypoint.setTripStatistics(waypointStatsBuilder.getStatistics());
providerUtils.updateWaypoint(waypoint);
}
}
}
@@ -1031,19 +994,14 @@ public class TrackRecordingService extends Service {
isRecording = false;
Track recordedTrack = providerUtils.getTrack(recordingTrackId);
if (recordedTrack != null) {
TripStatistics stats = recordedTrack.getTripStatistics();
stats.setStopTime(System.currentTimeMillis());
stats.setTotalTime(stats.getStopTime() - stats.getStartTime());
long lastRecordedLocationId =
providerUtils.getLastLocationId(recordingTrackId);
ContentValues values = new ContentValues();
long lastRecordedLocationId = providerUtils.getLastLocationId(recordingTrackId);
if (lastRecordedLocationId >= 0 && recordedTrack.getStopId() >= 0) {
values.put(TracksColumns.STOPID, lastRecordedLocationId);
recordedTrack.setStopId(lastRecordedLocationId);
}
values.put(TracksColumns.STOPTIME, stats.getStopTime());
values.put(TracksColumns.TOTALTIME, stats.getTotalTime());
getContentResolver().update(TracksColumns.CONTENT_URI, values,
"_id=" + recordedTrack.getId(), null);
TripStatistics tripStatistics = recordedTrack.getTripStatistics();
tripStatistics.setStopTime(System.currentTimeMillis());
tripStatistics.setTotalTime(tripStatistics.getStopTime() - tripStatistics.getStartTime());
providerUtils.updateTrack(recordedTrack);
}
showNotification();
long recordedTrackId = recordingTrackId;