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 eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 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.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.source=1.6 org.eclipse.jdt.core.compiler.source=1.6
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false 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=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=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=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=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=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=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_assignment=0
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 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_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 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=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=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_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 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=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=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_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=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_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=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=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=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=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.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_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=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_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_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.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.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=2 org.eclipse.jdt.core.formatter.continuation_indentation=2
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=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.disabling_tag=@formatter\:off
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on 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.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_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_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_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_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=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_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_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=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.join_wrapped_lines=true
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false 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_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.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.lineSplit=100 org.eclipse.jdt.core.formatter.lineSplit=100
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false 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_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.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true 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.char=space
org.eclipse.jdt.core.formatter.tabulation.size=2 org.eclipse.jdt.core.formatter.tabulation.size=2
org.eclipse.jdt.core.formatter.use_on_off_tags=false 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.use_tabs_only_for_leading_indentations=false
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true 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_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_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 eclipse.preferences.version=1
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
formatter_profile=_MyTracks formatter_profile=_My Tracks
formatter_settings_version=12 formatter_settings_version=13
org.eclipse.jdt.ui.ignorelowercasenames=true org.eclipse.jdt.ui.ignorelowercasenames=true
org.eclipse.jdt.ui.importorder=com;android;java;javax;org; org.eclipse.jdt.ui.importorder=com;android;java;javax;org;
org.eclipse.jdt.ui.ondemandthreshold=99 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" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="47" android:versionCode="48"
android:versionName="2.0" android:versionName="2.0"
package="com.google.android.maps.mytracks" > 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 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 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 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 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 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 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; package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.Constants.TAG; import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver; import android.database.ContentObserver;
import android.hardware.Sensor; import android.hardware.Sensor;
import android.hardware.SensorEvent; import android.hardware.SensorEvent;
import android.hardware.SensorEventListener; import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location; import android.location.Location;
import android.location.LocationListener; import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
@@ -23,243 +36,281 @@ import java.util.EnumSet;
import java.util.Set; import java.util.Set;
/** /**
* External data source manager, which converts system-level events into My Tracks data events. * Data source manager. Creates observers/listeners and manages their
* * registration with {@link DataSource}. The observers/listeners calls
* {@link DataSourceListener} when data changes.
*
* @author Rodrigo Damazio * @author Rodrigo Damazio
*/ */
class DataSourceManager { public class DataSourceManager {
/** Single interface for receiving system events that were registered for. */ /**
interface DataSourceListener { * Observer when the tracks table is updated.
void notifyTrackUpdated(); *
void notifyWaypointUpdated(); * @author Jimmy Shih
void notifyPointsUpdated(); */
void notifyPreferenceChanged(String key); private class TracksTableObserver extends ContentObserver {
void notifyLocationProviderEnabled(boolean enabled);
void notifyLocationProviderAvailable(boolean available);
void notifyLocationChanged(Location loc);
void notifyHeadingChanged(float heading);
}
private final DataSourceListener listener; public TracksTableObserver() {
super(handler);
/** Observer for when the tracks table is updated. */
private class TrackObserver extends ContentObserver {
public TrackObserver() {
super(contentHandler);
} }
@Override @Override
public void onChange(boolean selfChange) { public void onChange(boolean selfChange) {
listener.notifyTrackUpdated(); dataSourceListener.notifyTracksTableUpdated();
} }
} }
/** Observer for when the waypoints table is updated. */ /**
private class WaypointObserver extends ContentObserver { * Observer when the waypoints table is updated.
public WaypointObserver() { *
super(contentHandler); * @author Jimmy Shih
*/
private class WaypointsTableObserver extends ContentObserver {
public WaypointsTableObserver() {
super(handler);
} }
@Override @Override
public void onChange(boolean selfChange) { public void onChange(boolean selfChange) {
listener.notifyWaypointUpdated(); dataSourceListener.notifyWaypointsTableUpdated();
} }
} }
/** Observer for when the points table is updated. */ /**
private class PointObserver extends ContentObserver { * Observer when the track points table is updated.
public PointObserver() { *
super(contentHandler); * @author Jimmy Shih
*/
private class TrackPointsTableObserver extends ContentObserver {
public TrackPointsTableObserver() {
super(handler);
} }
@Override @Override
public void onChange(boolean selfChange) { public void onChange(boolean selfChange) {
listener.notifyPointsUpdated(); dataSourceListener.notifyTrackPointsTableUpdated();
} }
} }
/** Listener for when preferences change. */ /**
private class HubSharedPreferenceListener implements OnSharedPreferenceChangeListener { * Listener for location changes.
@Override *
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { * @author Jimmy Shih
listener.notifyPreferenceChanged(key); */
} private class CurrentLocationListener implements LocationListener {
}
/** Listener for the current location (independent from track data). */
private class CurrentLocationListener implements
LocationListener {
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
if (!LocationManager.GPS_PROVIDER.equals(provider)) return;
listener.notifyLocationProviderAvailable(status == LocationProvider.AVAILABLE);
}
@Override @Override
public void onProviderEnabled(String provider) { public void onLocationChanged(Location location) {
if (!LocationManager.GPS_PROVIDER.equals(provider)) return; dataSourceListener.notifyLocationChanged(location);
listener.notifyLocationProviderEnabled(true);
} }
@Override @Override
public void onProviderDisabled(String provider) { public void onProviderDisabled(String provider) {
if (!LocationManager.GPS_PROVIDER.equals(provider)) return; if (!LocationManager.GPS_PROVIDER.equals(provider)) {
return;
listener.notifyLocationProviderEnabled(false); }
dataSourceListener.notifyLocationProviderEnabled(false);
} }
@Override @Override
public void onLocationChanged(Location location) { public void onProviderEnabled(String provider) {
listener.notifyLocationChanged(location); if (!LocationManager.GPS_PROVIDER.equals(provider)) {
return;
}
dataSourceListener.notifyLocationProviderEnabled(true);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
if (!LocationManager.GPS_PROVIDER.equals(provider)) {
return;
}
dataSourceListener.notifyLocationProviderAvailable(status == LocationProvider.AVAILABLE);
} }
} }
/** Listener for compass readings. */ /**
private class CompassListener implements * Listener for compass changes.
SensorEventListener { *
@Override * @author Jimmy Shih
public void onSensorChanged(SensorEvent event) { */
listener.notifyHeadingChanged(event.values[0]); private class CompassListener implements SensorEventListener {
}
@Override @Override
public void onAccuracyChanged(Sensor sensor, int accuracy) { public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do nothing // Do nothing
} }
@Override
public void onSensorChanged(SensorEvent event) {
dataSourceListener.notifyHeadingChanged(event.values[0]);
}
} }
/** Wrapper for registering internal listeners. */ /**
private final DataSourcesWrapper dataSources; * Listener for preference changes.
*
* @author Jimmy Shih
*/
private class PreferenceListener implements OnSharedPreferenceChangeListener {
// Internal listeners (to receive data from the system) @Override
private final Set<ListenerDataType> registeredListeners = public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
EnumSet.noneOf(ListenerDataType.class); dataSourceListener.notifyPreferenceChanged(key);
private final Handler contentHandler; }
private final ContentObserver pointObserver; }
private final ContentObserver waypointObserver;
private final ContentObserver trackObserver;
private final LocationListener locationListener;
private final OnSharedPreferenceChangeListener preferenceListener;
private final SensorEventListener compassListener;
DataSourceManager(DataSourceListener listener, DataSourcesWrapper dataSources) { private final DataSource dataSource;
this.listener = listener; private final DataSourceListener dataSourceListener;
this.dataSources = dataSources;
contentHandler = new Handler(); // Registered listeners
pointObserver = new PointObserver(); private final Set<TrackDataType> registeredListeners = EnumSet.noneOf(TrackDataType.class);
waypointObserver = new WaypointObserver();
trackObserver = new TrackObserver(); private final Handler handler;
private final TracksTableObserver tracksTableObserver;
private final WaypointsTableObserver waypointsTableObserver;
private final TrackPointsTableObserver trackPointsTableObserver;
private final CurrentLocationListener currentLocationListener;
private final CompassListener compassListener;
private final PreferenceListener preferenceListener;
public DataSourceManager(DataSource dataSource, DataSourceListener dataSourceListener) {
this.dataSource = dataSource;
this.dataSourceListener = dataSourceListener;
handler = new Handler();
tracksTableObserver = new TracksTableObserver();
waypointsTableObserver = new WaypointsTableObserver();
trackPointsTableObserver = new TrackPointsTableObserver();
currentLocationListener = new CurrentLocationListener();
compassListener = new CompassListener(); compassListener = new CompassListener();
locationListener = new CurrentLocationListener(); preferenceListener = new PreferenceListener();
preferenceListener = new HubSharedPreferenceListener();
} }
/** Updates the internal (sensor, position, etc) listeners. */ /**
void updateAllListeners(EnumSet<ListenerDataType> externallyNeededListeners) { * Updates listeners with data source.
EnumSet<ListenerDataType> neededListeners = EnumSet.copyOf(externallyNeededListeners); *
* @param listeners the listeners
*/
public void updateListeners(EnumSet<TrackDataType> listeners) {
EnumSet<TrackDataType> neededListeners = EnumSet.copyOf(listeners);
// Special case - map sampled-out points type to points type since they /*
// correspond to the same internal listener. * Map SAMPLED_OUT_POINT_UPDATES to POINT_UPDATES since they correspond to
if (neededListeners.contains(ListenerDataType.SAMPLED_OUT_POINT_UPDATES)) { * the same internal listener
neededListeners.remove(ListenerDataType.SAMPLED_OUT_POINT_UPDATES); */
neededListeners.add(ListenerDataType.POINT_UPDATES); if (neededListeners.contains(TrackDataType.SAMPLED_OUT_TRACK_POINTS)) {
neededListeners.remove(TrackDataType.SAMPLED_OUT_TRACK_POINTS);
neededListeners.add(TrackDataType.TRACK_POINTS_TABLE);
} }
Log.d(TAG, "Updating internal listeners to types " + neededListeners); Log.d(TAG, "Updating listeners " + neededListeners);
// Unnecessary = registered - needed // Unnecessary = registered - needed
Set<ListenerDataType> unnecessaryListeners = EnumSet.copyOf(registeredListeners); Set<TrackDataType> unnecessaryListeners = EnumSet.copyOf(registeredListeners);
unnecessaryListeners.removeAll(neededListeners); unnecessaryListeners.removeAll(neededListeners);
// Missing = needed - registered // Missing = needed - registered
Set<ListenerDataType> missingListeners = EnumSet.copyOf(neededListeners); Set<TrackDataType> missingListeners = EnumSet.copyOf(neededListeners);
missingListeners.removeAll(registeredListeners); missingListeners.removeAll(registeredListeners);
// Remove all unnecessary listeners. // Remove unnecessary listeners
for (ListenerDataType type : unnecessaryListeners) { for (TrackDataType trackDataType : unnecessaryListeners) {
unregisterListener(type); unregisterListener(trackDataType);
} }
// Add all missing listeners. // Add missing listeners
for (ListenerDataType type : missingListeners) { for (TrackDataType trackDataType : missingListeners) {
registerListener(type); registerListener(trackDataType);
} }
// Now all needed types are registered. // Update registered listeners
registeredListeners.clear(); registeredListeners.clear();
registeredListeners.addAll(neededListeners); registeredListeners.addAll(neededListeners);
} }
private void registerListener(ListenerDataType type) { /**
switch (type) { * Registers a listener with data source.
case COMPASS_UPDATES: { *
// Listen to compass * @param trackDataType the listener data type
Sensor compass = dataSources.getSensor(Sensor.TYPE_ORIENTATION); */
if (compass != null) { private void registerListener(TrackDataType trackDataType) {
Log.d(TAG, "TrackDataHub: Now registering sensor listener."); switch (trackDataType) {
dataSources.registerSensorListener(compassListener, compass, SensorManager.SENSOR_DELAY_UI); case SELECTED_TRACK:
} // Do nothing
break; break;
} case TRACKS_TABLE:
case LOCATION_UPDATES: dataSource.registerContentObserver(TracksColumns.CONTENT_URI, tracksTableObserver);
dataSources.requestLocationUpdates(locationListener);
break; break;
case POINT_UPDATES: case WAYPOINTS_TABLE:
dataSources.registerContentObserver( dataSource.registerContentObserver(WaypointsColumns.CONTENT_URI, waypointsTableObserver);
TrackPointsColumns.CONTENT_URI, false, pointObserver);
break; break;
case TRACK_UPDATES: case TRACK_POINTS_TABLE:
dataSources.registerContentObserver(TracksColumns.CONTENT_URI, false, trackObserver); dataSource.registerContentObserver(
TrackPointsColumns.CONTENT_URI, trackPointsTableObserver);
break; break;
case WAYPOINT_UPDATES: case SAMPLED_OUT_TRACK_POINTS:
dataSources.registerContentObserver( // Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES.
WaypointsColumns.CONTENT_URI, false, waypointObserver);
break; break;
case DISPLAY_PREFERENCES: case LOCATION:
dataSources.registerOnSharedPreferenceChangeListener(preferenceListener); dataSource.registerLocationListener(currentLocationListener);
break;
case COMPASS:
dataSource.registerCompassListener(compassListener);
break;
case PREFERENCE:
dataSource.registerOnSharedPreferenceChangeListener(preferenceListener);
break;
default:
break; break;
case SAMPLED_OUT_POINT_UPDATES:
throw new IllegalArgumentException("Should have been mapped to point updates");
} }
} }
private void unregisterListener(ListenerDataType type) { /**
switch (type) { * Unregisters a listener with data source.
case COMPASS_UPDATES: *
dataSources.unregisterSensorListener(compassListener); * @param trackDataType listener data type
*/
private void unregisterListener(TrackDataType trackDataType) {
switch (trackDataType) {
case SELECTED_TRACK:
// Do nothing
break; break;
case LOCATION_UPDATES: case TRACKS_TABLE:
dataSources.removeLocationUpdates(locationListener); dataSource.unregisterContentObserver(tracksTableObserver);
break; break;
case POINT_UPDATES: case WAYPOINTS_TABLE:
dataSources.unregisterContentObserver(pointObserver); dataSource.unregisterContentObserver(waypointsTableObserver);
break; break;
case TRACK_UPDATES: case TRACK_POINTS_TABLE:
dataSources.unregisterContentObserver(trackObserver); dataSource.unregisterContentObserver(trackPointsTableObserver);
break; break;
case WAYPOINT_UPDATES: case SAMPLED_OUT_TRACK_POINTS:
dataSources.unregisterContentObserver(waypointObserver); // Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES.
break; break;
case DISPLAY_PREFERENCES: case LOCATION:
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListener); dataSource.unregisterLocationListener(currentLocationListener);
break;
case COMPASS:
dataSource.unregisterCompassListener(compassListener);
break;
case PREFERENCE:
dataSource.unregisterOnSharedPreferenceChangeListener(preferenceListener);
break;
default:
break; break;
case SAMPLED_OUT_POINT_UPDATES:
throw new IllegalArgumentException("Should have been mapped to point updates");
} }
} }
/** Unregisters all internal (sensor, position, etc.) listeners. */ /**
void unregisterAllListeners() { * Unregisters all listeners with data source.
dataSources.removeLocationUpdates(locationListener); */
dataSources.unregisterSensorListener(compassListener); public void unregisterAllListeners() {
dataSources.unregisterContentObserver(trackObserver); for (TrackDataType trackDataType : TrackDataType.values()) {
dataSources.unregisterContentObserver(waypointObserver); unregisterListener(trackDataType);
dataSources.unregisterContentObserver(pointObserver); }
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListener);
} }
} }
@@ -1,54 +0,0 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.location.Location;
import android.location.LocationListener;
import android.net.Uri;
/**
* Interface for abstracting registration of external data source listeners.
*
* @author Rodrigo Damazio
*/
interface DataSourcesWrapper {
// Preferences
void registerOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener);
void unregisterOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener);
// Content provider
void registerContentObserver(Uri contentUri, boolean descendents,
ContentObserver observer);
void unregisterContentObserver(ContentObserver observer);
// Sensors
Sensor getSensor(int type);
void registerSensorListener(SensorEventListener listener,
Sensor sensor, int sensorDelay);
void unregisterSensorListener(SensorEventListener listener);
// Location
boolean isLocationProviderEnabled(String provider);
void requestLocationUpdates(LocationListener listener);
void removeLocationUpdates(LocationListener listener);
Location getLastKnownLocation();
}
@@ -1,166 +0,0 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.Constants.MAX_LOCATION_AGE_MS;
import static com.google.android.apps.mytracks.Constants.MAX_NETWORK_AGE_MS;
import com.google.android.apps.mytracks.Constants;
import com.google.android.maps.mytracks.R;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
/**
* Real implementation of the data sources, which talks to system services.
*
* @author Rodrigo Damazio
*/
class DataSourcesWrapperImpl implements DataSourcesWrapper {
// System services
private final SensorManager sensorManager;
private final LocationManager locationManager;
private final ContentResolver contentResolver;
private final SharedPreferences sharedPreferences;
private final Context context;
DataSourcesWrapperImpl(Context context, SharedPreferences sharedPreferences) {
this.context = context;
this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
this.contentResolver = context.getContentResolver();
this.sharedPreferences = sharedPreferences;
}
@Override
public void registerOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener) {
sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
}
@Override
public void unregisterOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener) {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
}
@Override
public void registerContentObserver(Uri contentUri, boolean descendents,
ContentObserver observer) {
contentResolver.registerContentObserver(contentUri, descendents, observer);
}
@Override
public void unregisterContentObserver(ContentObserver observer) {
contentResolver.unregisterContentObserver(observer);
}
@Override
public Sensor getSensor(int type) {
return sensorManager.getDefaultSensor(type);
}
@Override
public void registerSensorListener(SensorEventListener listener,
Sensor sensor, int sensorDelay) {
sensorManager.registerListener(listener, sensor, sensorDelay);
}
@Override
public void unregisterSensorListener(SensorEventListener listener) {
sensorManager.unregisterListener(listener);
}
@Override
public boolean isLocationProviderEnabled(String provider) {
return locationManager.isProviderEnabled(provider);
}
@Override
public void requestLocationUpdates(LocationListener listener) {
// Check if the provider exists.
LocationProvider gpsProvider = locationManager.getProvider(LocationManager.GPS_PROVIDER);
if (gpsProvider == null) {
listener.onProviderDisabled(LocationManager.GPS_PROVIDER);
locationManager.removeUpdates(listener);
return;
}
// Listen to GPS location.
String providerName = gpsProvider.getName();
Log.d(Constants.TAG, "TrackDataHub: Using location provider " + providerName);
locationManager.requestLocationUpdates(providerName,
0 /*minTime*/, 0 /*minDist*/, listener);
// Give an initial update on provider state.
if (locationManager.isProviderEnabled(providerName)) {
listener.onProviderEnabled(providerName);
} else {
listener.onProviderDisabled(providerName);
}
// Listen to network location
try {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, listener);
} catch (RuntimeException e) {
// If anything at all goes wrong with getting a cell location do not
// abort. Cell location is not essential to this app.
Log.w(Constants.TAG, "Could not register network location listener.", e);
}
}
@Override
public Location getLastKnownLocation() {
// TODO: Let's look at more advanced algorithms to determine the best
// current location.
Location loc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
final long now = System.currentTimeMillis();
if (loc == null || loc.getTime() < now - MAX_LOCATION_AGE_MS) {
// We don't have a recent GPS fix, just use cell towers if available
loc = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
int toastResId = R.string.my_location_approximate_location;
if (loc == null || loc.getTime() < now - MAX_NETWORK_AGE_MS) {
// We don't have a recent cell tower location, let the user know:
toastResId = R.string.my_location_no_location;
}
// Let the user know we have only an approximate location:
Toast.makeText(context, toastResId, Toast.LENGTH_LONG).show();
}
return loc;
}
@Override
public void removeLocationUpdates(LocationListener listener) {
locationManager.removeUpdates(listener);
}
}
@@ -0,0 +1,105 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
import java.util.EnumSet;
/**
* State for a registered {@link TrackDataListener}.
*
* @author Jimmy Shih
*/
public class ListenerState {
private TrackDataListener trackDataListener;
private EnumSet<TrackDataType> trackDataTypes;
private long lastTrackId;
private long lastPointId;
private int lastSamplingFrequency;
private int numberOfLoadedPoints;
public ListenerState(
TrackDataListener trackDataListener, EnumSet<TrackDataType> trackDataTypes) {
this.trackDataListener = trackDataListener;
this.trackDataTypes = trackDataTypes;
}
/**
* Gets the {@link TrackDataListener}.
*/
public TrackDataListener getTrackDataListener() {
return trackDataListener;
}
/**
* Gets the track data types.
*/
public EnumSet<TrackDataType> getTrackDataTypes() {
return trackDataTypes;
}
/**
* Gets the last track id.
*/
public long getLastTrackId() {
return lastTrackId;
}
/**
* Gets the last point id.
*/
public long getLastPointId() {
return lastPointId;
}
/**
* Gets the last sampling frequency.
*/
public int getLastSamplingFrequency() {
return lastSamplingFrequency;
}
/**
* Get the number of loaded points.
*/
public int getNumberOfLoadedPoints() {
return numberOfLoadedPoints;
}
/**
* Sets the listener state.
*
* @param trackId track id
* @param pointId point id
* @param samplingFrequency sampling frequency
* @param loadedPoints number of loaded points
*/
public void setState(long trackId, long pointId, int samplingFrequency, int loadedPoints) {
lastTrackId = trackId;
lastPointId = pointId;
lastSamplingFrequency = samplingFrequency;
numberOfLoadedPoints = loadedPoints;
}
/**
* Resets state.
*/
public void resetState() {
setState(0L, 0L, 0, 0);
}
}
@@ -16,7 +16,6 @@
package com.google.android.apps.mytracks.content; 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.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R; import com.google.android.maps.mytracks.R;
@@ -38,64 +37,58 @@ import android.text.TextUtils;
import android.util.Log; 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 * @author Leif Hendrik Wilden
*/ */
public class MyTracksProvider extends ContentProvider { public class MyTracksProvider extends ContentProvider {
private static final String TAG = MyTracksProvider.class.getSimpleName();
private static final String DATABASE_NAME = "mytracks.db"; private static final String DATABASE_NAME = "mytracks.db";
private static final int DATABASE_VERSION = 20; 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 { private static class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context) { public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); super(context, DATABASE_NAME, null, DATABASE_VERSION);
} }
@Override @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
db.execSQL(TrackPointsColumns.CREATE_TABLE); db.execSQL(TrackPointsColumns.CREATE_TABLE);
db.execSQL(TracksColumns.CREATE_TABLE); db.execSQL(TracksColumns.CREATE_TABLE);
db.execSQL(WaypointsColumns.CREATE_TABLE); db.execSQL(WaypointsColumns.CREATE_TABLE);
} }
@Override @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion); Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion);
if (oldVersion < 17) { if (oldVersion < 17) {
// Delete the old data Log.w(TAG, "Deleting all old data.");
Log.w(TAG, "Delete all old data");
db.execSQL("DROP TABLE IF EXISTS " + TrackPointsColumns.TABLE_NAME); db.execSQL("DROP TABLE IF EXISTS " + TrackPointsColumns.TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + TracksColumns.TABLE_NAME); db.execSQL("DROP TABLE IF EXISTS " + TracksColumns.TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + WaypointsColumns.TABLE_NAME); db.execSQL("DROP TABLE IF EXISTS " + WaypointsColumns.TABLE_NAME);
onCreate(db); onCreate(db);
} else { } else {
// Incremental updates go here. For each DB version, add a corresponding if clause. // Incremental upgrades. One if statement per DB version.
// Track points sensor column // Add track points SENSOR column
if (oldVersion <= 17) { if (oldVersion <= 17) {
Log.w(TAG, "Upgrade DB: Adding sensor column."); Log.w(TAG, "Upgrade DB: Adding sensor column.");
db.execSQL("ALTER TABLE " + TrackPointsColumns.TABLE_NAME + " ADD " db.execSQL("ALTER TABLE " + TrackPointsColumns.TABLE_NAME + " ADD "
+ TrackPointsColumns.SENSOR + " BLOB"); + TrackPointsColumns.SENSOR + " BLOB");
} }
// Tracks table id column // Add tracks TABLEID column
if (oldVersion <= 18) { if (oldVersion <= 18) {
Log.w(TAG, "Upgrade DB: Adding tableid column."); Log.w(TAG, "Upgrade DB: Adding tableid column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.TABLEID db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.TABLEID
+ " STRING"); + " STRING");
} }
// Tracks table icon column // Add tracks ICON column
if (oldVersion <= 19) { if (oldVersion <= 19) {
Log.w(TAG, "Upgrade DB: Adding icon column."); Log.w(TAG, "Upgrade DB: Adding icon column.");
db.execSQL( 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; private SQLiteDatabase db;
public MyTracksProvider() { public MyTracksProvider() {
urlMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, uriMatcher.addURI(MyTracksProviderUtils.AUTHORITY, TrackPointsColumns.TABLE_NAME,
"trackpoints", TRACKPOINTS); UrlType.TRACKPOINTS.ordinal());
urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, uriMatcher.addURI(MyTracksProviderUtils.AUTHORITY, TrackPointsColumns.TABLE_NAME + "/#",
"trackpoints/#", TRACKPOINTS_ID); UrlType.TRACKPOINTS_ID.ordinal());
urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, "tracks", TRACKS); uriMatcher.addURI(
urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, "tracks/#", TRACKS_ID); MyTracksProviderUtils.AUTHORITY, TracksColumns.TABLE_NAME, UrlType.TRACKS.ordinal());
urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, "waypoints", WAYPOINTS); uriMatcher.addURI(MyTracksProviderUtils.AUTHORITY, TracksColumns.TABLE_NAME + "/#",
urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, UrlType.TRACKS_ID.ordinal());
"waypoints/#", WAYPOINTS_ID); uriMatcher.addURI(
} MyTracksProviderUtils.AUTHORITY, WaypointsColumns.TABLE_NAME, UrlType.WAYPOINTS.ordinal());
uriMatcher.addURI(MyTracksProviderUtils.AUTHORITY, WaypointsColumns.TABLE_NAME + "/#",
private boolean canAccess() { UrlType.WAYPOINTS_ID.ordinal());
if (Binder.getCallingPid() == Process.myPid()) {
return true;
} else {
return PreferencesUtils.getBoolean(
getContext(), R.string.allow_access_key, PreferencesUtils.ALLOW_ACCESS_DEFAULT);
}
} }
@Override @Override
@@ -136,11 +131,11 @@ public class MyTracksProvider extends ContentProvider {
if (!canAccess()) { if (!canAccess()) {
return false; return false;
} }
DatabaseHelper dbHelper = new DatabaseHelper(getContext()); DatabaseHelper databaseHelper = new DatabaseHelper(getContext());
try { try {
db = dbHelper.getWritableDatabase(); db = databaseHelper.getWritableDatabase();
} catch (SQLiteException e) { } 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; return db != null;
} }
@@ -152,7 +147,7 @@ public class MyTracksProvider extends ContentProvider {
} }
String table; String table;
boolean shouldVacuum = false; boolean shouldVacuum = false;
switch (urlMatcher.match(url)) { switch (getUrlType(url)) {
case TRACKPOINTS: case TRACKPOINTS:
table = TrackPointsColumns.TABLE_NAME; table = TrackPointsColumns.TABLE_NAME;
break; break;
@@ -166,17 +161,23 @@ public class MyTracksProvider extends ContentProvider {
default: default:
throw new IllegalArgumentException("Unknown URL " + url); throw new IllegalArgumentException("Unknown URL " + url);
} }
Log.w(MyTracksProvider.TAG, "provider delete in " + table + "!"); Log.w(MyTracksProvider.TAG, "Deleting table " + table);
int count = db.delete(table, where, selectionArgs); int count;
try {
db.beginTransaction();
count = db.delete(table, where, selectionArgs);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
getContext().getContentResolver().notifyChange(url, null, true); getContext().getContentResolver().notifyChange(url, null, true);
if (shouldVacuum) { if (shouldVacuum) {
// If a potentially large amount of data was deleted, we want to reclaim its space. // If a potentially large amount of data was deleted, reclaim its space.
Log.i(TAG, "Vacuuming the database"); Log.i(TAG, "Vacuuming the database.");
db.execSQL("VACUUM"); db.execSQL("VACUUM");
} }
return count; return count;
} }
@@ -185,7 +186,7 @@ public class MyTracksProvider extends ContentProvider {
if (!canAccess()) { if (!canAccess()) {
return null; return null;
} }
switch (urlMatcher.match(url)) { switch (getUrlType(url)) {
case TRACKPOINTS: case TRACKPOINTS:
return TrackPointsColumns.CONTENT_TYPE; return TrackPointsColumns.CONTENT_TYPE;
case TRACKPOINTS_ID: case TRACKPOINTS_ID:
@@ -208,197 +209,240 @@ public class MyTracksProvider extends ContentProvider {
if (!canAccess()) { if (!canAccess()) {
return null; return null;
} }
Log.d(MyTracksProvider.TAG, "MyTracksProvider.insert"); if (initialValues == null) {
ContentValues values; initialValues = new ContentValues();
if (initialValues != null) {
values = initialValues;
} else {
values = new ContentValues();
} }
Uri result = null;
int urlMatchType = urlMatcher.match(url); try {
return insertType(url, urlMatchType, values); db.beginTransaction();
} result = insertContentValues(url, getUrlType(url), initialValues);
db.setTransactionSuccessful();
private Uri insertType(Uri url, int urlMatchType, ContentValues values) { } finally {
switch (urlMatchType) { db.endTransaction();
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);
} }
return result;
} }
@Override @Override
public int bulkInsert(Uri url, ContentValues[] valuesBulk) { public int bulkInsert(Uri url, ContentValues[] valuesBulk) {
if (!canAccess()) { if (!canAccess()) {
return 0; return 0;
} }
Log.d(MyTracksProvider.TAG, "MyTracksProvider.bulkInsert");
int numInserted = 0; int numInserted = 0;
try { try {
// Use a transaction in order to make the insertions run as a single batch // Use a transaction in order to make the insertions run as a single batch
db.beginTransaction(); db.beginTransaction();
int urlMatch = urlMatcher.match(url); UrlType urlType = getUrlType(url);
for (numInserted = 0; numInserted < valuesBulk.length; numInserted++) { for (numInserted = 0; numInserted < valuesBulk.length; numInserted++) {
ContentValues values = valuesBulk[numInserted]; ContentValues contentValues = valuesBulk[numInserted];
if (values == null) { values = new ContentValues(); } if (contentValues == null) {
contentValues = new ContentValues();
insertType(url, urlMatch, values); }
insertContentValues(url, urlType, contentValues);
} }
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();
} }
return numInserted; 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 @Override
public Cursor query( public Cursor query(
Uri url, String[] projection, String selection, String[] selectionArgs, Uri url, String[] projection, String selection, String[] selectionArgs, String sort) {
String sort) {
if (!canAccess()) { if (!canAccess()) {
return null; return null;
} }
SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
int match = urlMatcher.match(url);
String sortOrder = null; String sortOrder = null;
if (match == TRACKPOINTS) { switch (getUrlType(url)) {
qb.setTables(TrackPointsColumns.TABLE_NAME); case TRACKPOINTS:
if (sort != null) { queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
sortOrder = sort; sortOrder = sort != null ? sort : TrackPointsColumns.DEFAULT_SORT_ORDER;
} else { break;
sortOrder = TrackPointsColumns.DEFAULT_SORT_ORDER; case TRACKPOINTS_ID:
} queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
} else if (match == TRACKPOINTS_ID) { queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1));
qb.setTables(TrackPointsColumns.TABLE_NAME); break;
qb.appendWhere("_id=" + url.getPathSegments().get(1)); case TRACKS:
} else if (match == TRACKS) { queryBuilder.setTables(TracksColumns.TABLE_NAME);
qb.setTables(TracksColumns.TABLE_NAME); sortOrder = sort != null ? sort : TracksColumns.DEFAULT_SORT_ORDER;
if (sort != null) { break;
sortOrder = sort; case TRACKS_ID:
} else { queryBuilder.setTables(TracksColumns.TABLE_NAME);
sortOrder = TracksColumns.DEFAULT_SORT_ORDER; queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1));
} break;
} else if (match == TRACKS_ID) { case WAYPOINTS:
qb.setTables(TracksColumns.TABLE_NAME); queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
qb.appendWhere("_id=" + url.getPathSegments().get(1)); sortOrder = sort != null ? sort : WaypointsColumns.DEFAULT_SORT_ORDER;
} else if (match == WAYPOINTS) { break;
qb.setTables(WaypointsColumns.TABLE_NAME); case WAYPOINTS_ID:
if (sort != null) { queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
sortOrder = sort; queryBuilder.appendWhere("_id=" + url.getPathSegments().get(1));
} else { break;
sortOrder = WaypointsColumns.DEFAULT_SORT_ORDER; default:
} throw new IllegalArgumentException("Unknown url " + url);
} else if (match == WAYPOINTS_ID) {
qb.setTables(WaypointsColumns.TABLE_NAME);
qb.appendWhere("_id=" + url.getPathSegments().get(1));
} else {
throw new IllegalArgumentException("Unknown URL " + url);
} }
Log.i(Constants.TAG, "Build query: " Cursor cursor = queryBuilder.query(
+ qb.buildQuery(projection, selection, selectionArgs, null, null, sortOrder, null)); db, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), url);
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, return cursor;
sortOrder);
c.setNotificationUri(getContext().getContentResolver(), url);
return c;
} }
@Override @Override
public int update(Uri url, ContentValues values, String where, public int update(Uri url, ContentValues values, String where, String[] selectionArgs) {
String[] selectionArgs) {
if (!canAccess()) { if (!canAccess()) {
return 0; 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 count;
int match = urlMatcher.match(url); try {
if (match == TRACKPOINTS) { db.beginTransaction();
count = db.update(TrackPointsColumns.TABLE_NAME, values, where, selectionArgs); count = db.update(table, values, whereClause, selectionArgs);
} else if (match == TRACKPOINTS_ID) { db.setTransactionSuccessful();
String segment = url.getPathSegments().get(1); } finally {
count = db.update(TrackPointsColumns.TABLE_NAME, values, "_id=" + segment db.endTransaction();
+ (!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);
} }
getContext().getContentResolver().notifyChange(url, null, true); getContext().getContentResolver().notifyChange(url, null, true);
return count; 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.TAG;
import static com.google.android.apps.mytracks.Constants.TARGET_DISPLAYED_TRACK_POINTS; import static com.google.android.apps.mytracks.Constants.TARGET_DISPLAYED_TRACK_POINTS;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.DataSourceManager.DataSourceListener;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.DoubleBufferedLocationFactory; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.DoubleBufferedLocationFactory;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.TrackDataListener.ProviderState; import com.google.android.apps.mytracks.content.TrackDataListener.ProviderState;
import com.google.android.apps.mytracks.content.TrackDataListeners.ListenerRegistration;
import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R; import com.google.android.maps.mytracks.R;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor; import android.database.Cursor;
import android.hardware.GeomagneticField; import android.hardware.GeomagneticField;
import android.location.Location; import android.location.Location;
@@ -60,54 +56,23 @@ public class TrackDataHub {
// Overridable constants // Overridable constants
private final int targetNumPoints; private final int targetNumPoints;
/** Types of data that we can expose. */
public static enum ListenerDataType {
/** Listen to when the selected track changes. */
SELECTED_TRACK_CHANGED,
/** Listen to when the tracks change. */
TRACK_UPDATES,
/** Listen to when the waypoints change. */
WAYPOINT_UPDATES,
/** Listen to when the current track points change. */
POINT_UPDATES,
/**
* Listen to sampled-out points.
* Listening to this without listening to {@link #POINT_UPDATES}
* makes no sense and may yield unexpected results.
*/
SAMPLED_OUT_POINT_UPDATES,
/** Listen to updates to the current location. */
LOCATION_UPDATES,
/** Listen to updates to the current heading. */
COMPASS_UPDATES,
/** Listens to changes in display preferences. */
DISPLAY_PREFERENCES;
}
/** Listener which receives events from the system. */ /** Listener which receives events from the system. */
private class HubDataSourceListener implements DataSourceListener { private class HubDataSourceListener implements DataSourceListener {
@Override @Override
public void notifyTrackUpdated() { public void notifyTracksTableUpdated() {
TrackDataHub.this.notifyTrackUpdated(getListenersFor(ListenerDataType.TRACK_UPDATES)); TrackDataHub.this.notifyTrackUpdated(getListenersFor(TrackDataType.TRACKS_TABLE));
} }
@Override @Override
public void notifyWaypointUpdated() { public void notifyWaypointsTableUpdated() {
TrackDataHub.this.notifyWaypointUpdated(getListenersFor(ListenerDataType.WAYPOINT_UPDATES)); TrackDataHub.this.notifyWaypointUpdated(getListenersFor(TrackDataType.WAYPOINTS_TABLE));
} }
@Override @Override
public void notifyPointsUpdated() { public void notifyTrackPointsTableUpdated() {
TrackDataHub.this.notifyPointsUpdated(true, 0, 0, TrackDataHub.this.notifyPointsUpdated(true, 0, 0,
getListenersFor(ListenerDataType.POINT_UPDATES), getListenersFor(TrackDataType.TRACK_POINTS_TABLE),
getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES)); getListenersFor(TrackDataType.SAMPLED_OUT_TRACK_POINTS));
} }
@Override @Override
@@ -130,21 +95,20 @@ public class TrackDataHub {
@Override @Override
public void notifyLocationChanged(Location loc) { public void notifyLocationChanged(Location loc) {
TrackDataHub.this.notifyLocationChanged(loc, TrackDataHub.this.notifyLocationChanged(loc,
getListenersFor(ListenerDataType.LOCATION_UPDATES)); getListenersFor(TrackDataType.LOCATION));
} }
@Override @Override
public void notifyHeadingChanged(float heading) { public void notifyHeadingChanged(float heading) {
lastSeenMagneticHeading = heading; lastSeenMagneticHeading = heading;
maybeUpdateDeclination(); maybeUpdateDeclination();
TrackDataHub.this.notifyHeadingChanged(getListenersFor(ListenerDataType.COMPASS_UPDATES)); TrackDataHub.this.notifyHeadingChanged(getListenersFor(TrackDataType.COMPASS));
} }
} }
// Application services // Application services
private final Context context; private final Context context;
private final MyTracksProviderUtils providerUtils; private final MyTracksProviderUtils providerUtils;
private final SharedPreferences preferences;
// Get content notifications on the main thread, send listener callbacks in another. // Get content notifications on the main thread, send listener callbacks in another.
// This ensures listener calls are serialized. // This ensures listener calls are serialized.
@@ -152,12 +116,9 @@ public class TrackDataHub {
private Handler listenerHandler; private Handler listenerHandler;
/** Manager for external listeners (those from activities). */ /** Manager for external listeners (those from activities). */
private final TrackDataListeners dataListeners; private final TrackDataManager trackDataManager;
/** Wrapper for interacting with system data managers. */ private DataSource dataSource;
private DataSourcesWrapper dataSources;
/** Manager for system data listener registrations. */
private DataSourceManager dataSourceManager; private DataSourceManager dataSourceManager;
/** Condensed listener for system data listener events. */ /** Condensed listener for system data listener events. */
@@ -193,24 +154,19 @@ public class TrackDataHub {
* Builds a new {@link TrackDataHub} instance. * Builds a new {@link TrackDataHub} instance.
*/ */
public synchronized static TrackDataHub newInstance(Context context) { public synchronized static TrackDataHub newInstance(Context context) {
SharedPreferences preferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(context); MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(context);
return new TrackDataHub(context, return new TrackDataHub(
new TrackDataListeners(), context, new TrackDataManager(), providerUtils, TARGET_DISPLAYED_TRACK_POINTS);
preferences, providerUtils,
TARGET_DISPLAYED_TRACK_POINTS);
} }
/** /**
* Injection constructor. * Injection constructor.
*/ */
// @VisibleForTesting // @VisibleForTesting
TrackDataHub(Context ctx, TrackDataListeners listeners, SharedPreferences preferences, TrackDataHub(Context ctx, TrackDataManager trackDataManager, MyTracksProviderUtils providerUtils,
MyTracksProviderUtils providerUtils, int targetNumPoints) { int targetNumPoints) {
this.context = ctx; this.context = ctx;
this.dataListeners = listeners; this.trackDataManager = trackDataManager;
this.preferences = preferences;
this.providerUtils = providerUtils; this.providerUtils = providerUtils;
this.targetNumPoints = targetNumPoints; this.targetNumPoints = targetNumPoints;
this.locationFactory = new DoubleBufferedLocationFactory(); this.locationFactory = new DoubleBufferedLocationFactory();
@@ -233,12 +189,12 @@ public class TrackDataHub {
listenerHandlerThread = new HandlerThread("trackDataContentThread"); listenerHandlerThread = new HandlerThread("trackDataContentThread");
listenerHandlerThread.start(); listenerHandlerThread.start();
listenerHandler = new Handler(listenerHandlerThread.getLooper()); listenerHandler = new Handler(listenerHandlerThread.getLooper());
dataSources = newDataSources(); dataSource = newDataSource();
dataSourceManager = new DataSourceManager(dataSourceListener, dataSources); dataSourceManager = new DataSourceManager(dataSource, dataSourceListener);
// This may or may not register internal listeners, depending on whether // This may or may not register internal listeners, depending on whether
// we already had external listeners. // we already had external listeners.
dataSourceManager.updateAllListeners(getNeededListenerTypes()); dataSourceManager.updateListeners(getNeededListenerTypes());
loadSharedPreferences(); loadSharedPreferences();
// If there were listeners already registered, make sure they become up-to-date. // If there were listeners already registered, make sure they become up-to-date.
@@ -246,8 +202,8 @@ public class TrackDataHub {
} }
// @VisibleForTesting // @VisibleForTesting
protected DataSourcesWrapper newDataSources() { protected DataSource newDataSource() {
return new DataSourcesWrapperImpl(context, preferences); return new DataSource(context);
} }
/** /**
@@ -267,7 +223,7 @@ public class TrackDataHub {
started = false; started = false;
dataSources = null; dataSource = null;
dataSourceManager = null; dataSourceManager = null;
listenerHandlerThread = null; listenerHandlerThread = null;
listenerHandler = null; listenerHandler = null;
@@ -343,9 +299,9 @@ public class TrackDataHub {
} }
Log.i(TAG, "Forcing location update"); Log.i(TAG, "Forcing location update");
Location loc = dataSources.getLastKnownLocation(); Location loc = dataSource.getLastKnownLocation();
if (loc != null) { if (loc != null) {
notifyLocationChanged(loc, getListenersFor(ListenerDataType.LOCATION_UPDATES)); notifyLocationChanged(loc, getListenersFor(TrackDataType.LOCATION));
} }
} }
@@ -415,30 +371,31 @@ public class TrackDataHub {
} }
public void registerTrackDataListener( public void registerTrackDataListener(
TrackDataListener listener, EnumSet<ListenerDataType> dataTypes) { TrackDataListener listener, EnumSet<TrackDataType> dataTypes) {
synchronized (dataListeners) { synchronized (trackDataManager) {
ListenerRegistration registration = ListenerState listenerState = trackDataManager.registerListener(listener, dataTypes);
dataListeners.registerTrackDataListener(listener, dataTypes);
// Don't load any data or start internal listeners if start() hasn't been // Don't load any data or start internal listeners if start() hasn't been
// called. When it is called, we'll do both things. // called. When it is called, we'll do both things.
if (!isStarted()) return; if (!isStarted()) {
return;
}
loadNewDataForListener(registration); loadNewDataForListener(listenerState);
dataSourceManager.updateAllListeners(getNeededListenerTypes()); dataSourceManager.updateListeners(getNeededListenerTypes());
} }
} }
public void unregisterTrackDataListener(TrackDataListener listener) { public void unregisterTrackDataListener(TrackDataListener listener) {
synchronized (dataListeners) { synchronized (trackDataManager) {
dataListeners.unregisterTrackDataListener(listener); trackDataManager.unregisterListener(listener);
// Don't load any data or start internal listeners if start() hasn't been // Don't load any data or start internal listeners if start() hasn't been
// called. When it is called, we'll do both things. // called. When it is called, we'll do both things.
if (!isStarted()) return; if (!isStarted()) return;
dataSourceManager.updateAllListeners(getNeededListenerTypes()); dataSourceManager.updateListeners(getNeededListenerTypes());
} }
} }
@@ -446,33 +403,32 @@ public class TrackDataHub {
* Reloads all track data received so far into the specified listeners. * Reloads all track data received so far into the specified listeners.
*/ */
public void reloadDataForListener(TrackDataListener listener) { public void reloadDataForListener(TrackDataListener listener) {
ListenerRegistration registration; synchronized (trackDataManager) {
synchronized (dataListeners) { ListenerState listenerState = trackDataManager.getListenerState(listener);
registration = dataListeners.getRegistration(listener); listenerState.resetState();
registration.resetState(); loadNewDataForListener(listenerState);
loadNewDataForListener(registration);
} }
} }
/** /**
* Reloads all track data received so far into the specified listeners. * Reloads all track data received so far into the specified listeners.
* *
* Assumes it's called from a block that synchronizes on {@link #dataListeners}. * Assumes it's called from a block that synchronizes on {@link #trackDataManager}.
*/ */
private void loadNewDataForListener(final ListenerRegistration registration) { private void loadNewDataForListener(final ListenerState listenerState) {
if (!isStarted()) { if (!isStarted()) {
Log.w(TAG, "Not started, not reloading"); Log.w(TAG, "Not started, not reloading");
return; return;
} }
if (registration == null) { if (listenerState == null) {
Log.w(TAG, "Not reloading for null registration"); Log.w(TAG, "Not reloading for null listener state");
return; return;
} }
// If a listener happens to be added after this method but before the Runnable below is // If a listener happens to be added after this method but before the Runnable below is
// executed, it will have triggered a separate call to load data only up to the point this // executed, it will have triggered a separate call to load data only up to the point this
// listener got to. This is ensured by being synchronized on listeners. // listener got to. This is ensured by being synchronized on listeners.
final boolean isOnlyListener = (dataListeners.getNumListeners() == 1); final boolean isOnlyListener = (trackDataManager.getNumberOfListeners() == 1);
runInListenerThread(new Runnable() { runInListenerThread(new Runnable() {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -480,30 +436,31 @@ public class TrackDataHub {
public void run() { public void run() {
// Reload everything if either it's a different track, or the track has been resampled // Reload everything if either it's a different track, or the track has been resampled
// (this also covers the case of a new registration). // (this also covers the case of a new registration).
boolean reloadAll = registration.lastTrackId != selectedTrackId || boolean reloadAll = listenerState.getLastTrackId() != selectedTrackId ||
registration.lastSamplingFrequency != lastSamplingFrequency; listenerState.getLastSamplingFrequency() != lastSamplingFrequency;
Log.d(TAG, "Doing a " + (reloadAll ? "full" : "partial") + " reload for " + registration); Log.d(TAG, "Doing a " + (reloadAll ? "full" : "partial") + " reload for " + listenerState);
TrackDataListener listener = registration.listener; TrackDataListener listener = listenerState.getTrackDataListener();
Set<TrackDataListener> listenerSet = Collections.singleton(listener); Set<TrackDataListener> listenerSet = Collections.singleton(listener);
if (registration.isInterestedIn(ListenerDataType.DISPLAY_PREFERENCES)) { EnumSet<TrackDataType> trackDataTypes = listenerState.getTrackDataTypes();
if (trackDataTypes.contains(TrackDataType.PREFERENCE)) {
reloadAll |= listener.onUnitsChanged(metricUnits); reloadAll |= listener.onUnitsChanged(metricUnits);
reloadAll |= listener.onReportSpeedChanged(reportSpeed); reloadAll |= listener.onReportSpeedChanged(reportSpeed);
} }
if (reloadAll && registration.isInterestedIn(ListenerDataType.SELECTED_TRACK_CHANGED)) { if (reloadAll && trackDataTypes.contains(TrackDataType.SELECTED_TRACK)) {
notifySelectedTrackChanged(selectedTrackId, listenerSet); notifySelectedTrackChanged(selectedTrackId, listenerSet);
} }
if (registration.isInterestedIn(ListenerDataType.TRACK_UPDATES)) { if (trackDataTypes.contains(TrackDataType.TRACKS_TABLE)) {
notifyTrackUpdated(listenerSet); notifyTrackUpdated(listenerSet);
} }
boolean interestedInPoints = boolean interestedInPoints = trackDataTypes.contains(TrackDataType.TRACK_POINTS_TABLE);
registration.isInterestedIn(ListenerDataType.POINT_UPDATES); boolean interestedInSampledOutPoints = trackDataTypes.contains(
boolean interestedInSampledOutPoints = TrackDataType.SAMPLED_OUT_TRACK_POINTS);
registration.isInterestedIn(ListenerDataType.SAMPLED_OUT_POINT_UPDATES);
if (interestedInPoints || interestedInSampledOutPoints) { if (interestedInPoints || interestedInSampledOutPoints) {
long minPointId = 0; long minPointId = 0;
int previousNumPoints = 0; int previousNumPoints = 0;
@@ -513,8 +470,8 @@ public class TrackDataHub {
notifyPointsCleared(listenerSet); notifyPointsCleared(listenerSet);
} else { } else {
// Send only new points // Send only new points
minPointId = registration.lastPointId + 1; minPointId = listenerState.getLastPointId() + 1;
previousNumPoints = registration.numLoadedPoints; previousNumPoints = listenerState.getNumberOfLoadedPoints();
} }
// If this is the only listener we have registered, keep the state that we serve to it as // If this is the only listener we have registered, keep the state that we serve to it as
@@ -530,11 +487,11 @@ public class TrackDataHub {
interestedInSampledOutPoints ? listenerSet : Collections.EMPTY_SET); interestedInSampledOutPoints ? listenerSet : Collections.EMPTY_SET);
} }
if (registration.isInterestedIn(ListenerDataType.WAYPOINT_UPDATES)) { if (trackDataTypes.contains(TrackDataType.WAYPOINTS_TABLE)) {
notifyWaypointUpdated(listenerSet); notifyWaypointUpdated(listenerSet);
} }
if (registration.isInterestedIn(ListenerDataType.LOCATION_UPDATES)) { if (trackDataTypes.contains(TrackDataType.LOCATION)) {
if (lastSeenLocation != null) { if (lastSeenLocation != null) {
notifyLocationChanged(lastSeenLocation, true, listenerSet); notifyLocationChanged(lastSeenLocation, true, listenerSet);
} else { } else {
@@ -542,7 +499,7 @@ public class TrackDataHub {
} }
} }
if (registration.isInterestedIn(ListenerDataType.COMPASS_UPDATES)) { if (trackDataTypes.contains(TrackDataType.COMPASS)) {
notifyHeadingChanged(listenerSet); notifyHeadingChanged(listenerSet);
} }
} }
@@ -557,8 +514,8 @@ public class TrackDataHub {
Log.w(TAG, "Not started, not reloading"); Log.w(TAG, "Not started, not reloading");
return; return;
} }
synchronized (dataListeners) { synchronized (trackDataManager) {
if (!dataListeners.hasListeners()) { if (trackDataManager.getNumberOfListeners() == 0) {
Log.d(TAG, "No listeners, not reloading"); Log.d(TAG, "No listeners, not reloading");
return; return;
} }
@@ -569,33 +526,33 @@ public class TrackDataHub {
public void run() { public void run() {
// Ignore the return values here, we're already sending the full data set anyway // Ignore the return values here, we're already sending the full data set anyway
for (TrackDataListener listener : for (TrackDataListener listener :
getListenersFor(ListenerDataType.DISPLAY_PREFERENCES)) { getListenersFor(TrackDataType.PREFERENCE)) {
listener.onUnitsChanged(metricUnits); listener.onUnitsChanged(metricUnits);
listener.onReportSpeedChanged(reportSpeed); listener.onReportSpeedChanged(reportSpeed);
} }
notifySelectedTrackChanged(selectedTrackId, notifySelectedTrackChanged(selectedTrackId,
getListenersFor(ListenerDataType.SELECTED_TRACK_CHANGED)); getListenersFor(TrackDataType.SELECTED_TRACK));
notifyTrackUpdated(getListenersFor(ListenerDataType.TRACK_UPDATES)); notifyTrackUpdated(getListenersFor(TrackDataType.TRACKS_TABLE));
Set<TrackDataListener> pointListeners = Set<TrackDataListener> pointListeners =
getListenersFor(ListenerDataType.POINT_UPDATES); getListenersFor(TrackDataType.TRACK_POINTS_TABLE);
Set<TrackDataListener> sampledOutPointListeners = Set<TrackDataListener> sampledOutPointListeners =
getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES); getListenersFor(TrackDataType.SAMPLED_OUT_TRACK_POINTS);
notifyPointsCleared(pointListeners); notifyPointsCleared(pointListeners);
notifyPointsUpdated(true, 0, 0, pointListeners, sampledOutPointListeners); notifyPointsUpdated(true, 0, 0, pointListeners, sampledOutPointListeners);
notifyWaypointUpdated(getListenersFor(ListenerDataType.WAYPOINT_UPDATES)); notifyWaypointUpdated(getListenersFor(TrackDataType.WAYPOINTS_TABLE));
if (lastSeenLocation != null) { if (lastSeenLocation != null) {
notifyLocationChanged(lastSeenLocation, true, notifyLocationChanged(lastSeenLocation, true,
getListenersFor(ListenerDataType.LOCATION_UPDATES)); getListenersFor(TrackDataType.LOCATION));
} else { } else {
notifyFixType(); notifyFixType();
} }
notifyHeadingChanged(getListenersFor(ListenerDataType.COMPASS_UPDATES)); notifyHeadingChanged(getListenersFor(TrackDataType.COMPASS));
} }
}); });
} }
@@ -630,12 +587,12 @@ public class TrackDataHub {
@Override @Override
public void run() { public void run() {
Set<TrackDataListener> displayListeners = Set<TrackDataListener> displayListeners =
getListenersFor(ListenerDataType.DISPLAY_PREFERENCES); getListenersFor(TrackDataType.PREFERENCE);
for (TrackDataListener listener : displayListeners) { for (TrackDataListener listener : displayListeners) {
// TODO: Do the reloading just once for all interested listeners // TODO: Do the reloading just once for all interested listeners
if (listener.onReportSpeedChanged(reportSpeed)) { if (listener.onReportSpeedChanged(reportSpeed)) {
synchronized (dataListeners) { synchronized (trackDataManager) {
reloadDataForListener(listener); reloadDataForListener(listener);
} }
} }
@@ -651,11 +608,11 @@ public class TrackDataHub {
runInListenerThread(new Runnable() { runInListenerThread(new Runnable() {
@Override @Override
public void run() { public void run() {
Set<TrackDataListener> displayListeners = getListenersFor(ListenerDataType.DISPLAY_PREFERENCES); Set<TrackDataListener> displayListeners = getListenersFor(TrackDataType.PREFERENCE);
for (TrackDataListener listener : displayListeners) { for (TrackDataListener listener : displayListeners) {
if (listener.onUnitsChanged(metricUnits)) { if (listener.onUnitsChanged(metricUnits)) {
synchronized (dataListeners) { synchronized (trackDataManager) {
reloadDataForListener(listener); reloadDataForListener(listener);
} }
} }
@@ -683,7 +640,7 @@ public class TrackDataHub {
// Notify to everyone. // Notify to everyone.
Log.d(TAG, "Notifying fix type: " + state); Log.d(TAG, "Notifying fix type: " + state);
for (TrackDataListener listener : for (TrackDataListener listener :
getListenersFor(ListenerDataType.LOCATION_UPDATES)) { getListenersFor(TrackDataType.LOCATION)) {
listener.onProviderStateChange(state); listener.onProviderStateChange(state);
} }
} }
@@ -945,9 +902,9 @@ public class TrackDataHub {
Log.i(TAG, "Resampling point set after " + numLoadedPoints + " points."); Log.i(TAG, "Resampling point set after " + numLoadedPoints + " points.");
resetState(); resetState();
synchronized (dataListeners) { synchronized (trackDataManager) {
sampledListeners = getListenersFor(ListenerDataType.POINT_UPDATES); sampledListeners = getListenersFor(TrackDataType.TRACK_POINTS_TABLE);
sampledOutListeners = getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES); sampledOutListeners = getListenersFor(TrackDataType.SAMPLED_OUT_TRACK_POINTS);
} }
maxPointId = -1; maxPointId = -1;
minPointId = 0; minPointId = 0;
@@ -1030,12 +987,10 @@ public class TrackDataHub {
listener.onNewTrackPointsDone(); listener.onNewTrackPointsDone();
// Update the listener state // Update the listener state
ListenerRegistration registration = dataListeners.getRegistration(listener); ListenerState listenerState = trackDataManager.getListenerState(listener);
if (registration != null) { if (listenerState != null) {
registration.lastTrackId = currentSelectedTrackId; listenerState.setState(currentSelectedTrackId, localLastSeenLocationId,
registration.lastPointId = localLastSeenLocationId; pointSamplingFrequency, localNumLoadedPoints);
registration.lastSamplingFrequency = pointSamplingFrequency;
registration.numLoadedPoints = localNumLoadedPoints;
} }
} }
} }
@@ -1089,17 +1044,17 @@ public class TrackDataHub {
listenerHandler.post(runnable); listenerHandler.post(runnable);
} }
private Set<TrackDataListener> getListenersFor(ListenerDataType type) { private Set<TrackDataListener> getListenersFor(TrackDataType type) {
synchronized (dataListeners) { synchronized (trackDataManager) {
return dataListeners.getListenersFor(type); return trackDataManager.getListeners(type);
} }
} }
private EnumSet<ListenerDataType> getNeededListenerTypes() { private EnumSet<TrackDataType> getNeededListenerTypes() {
EnumSet<ListenerDataType> neededTypes = dataListeners.getAllRegisteredTypes(); EnumSet<TrackDataType> neededTypes = trackDataManager.getRegisteredTrackDataTypes();
// We always want preference updates. // We always want preference updates.
neededTypes.add(ListenerDataType.DISPLAY_PREFERENCES); neededTypes.add(TrackDataType.PREFERENCE);
return neededTypes; return neededTypes;
} }
@@ -1,159 +0,0 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import android.util.Log;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* Manager for the external data listeners and their listening types.
*
* @author Rodrigo Damazio
*/
class TrackDataListeners {
/** Internal representation of a listener's registration. */
static class ListenerRegistration {
final TrackDataListener listener;
final EnumSet<ListenerDataType> types;
// State that was last notified to the listener, for resuming after a pause.
long lastTrackId;
long lastPointId;
int lastSamplingFrequency;
int numLoadedPoints;
public ListenerRegistration(TrackDataListener listener,
EnumSet<ListenerDataType> types) {
this.listener = listener;
this.types = types;
}
public boolean isInterestedIn(ListenerDataType type) {
return types.contains(type);
}
public void resetState() {
lastTrackId = 0L;
lastPointId = 0L;
lastSamplingFrequency = 0;
numLoadedPoints = 0;
}
@Override
public String toString() {
return "ListenerRegistration [listener=" + listener + ", types=" + types
+ ", lastTrackId=" + lastTrackId + ", lastPointId=" + lastPointId
+ ", lastSamplingFrequency=" + lastSamplingFrequency
+ ", numLoadedPoints=" + numLoadedPoints + "]";
}
}
/** Map of external listener to its registration details. */
private final Map<TrackDataListener, ListenerRegistration> registeredListeners =
new HashMap<TrackDataListener, ListenerRegistration>();
/** Map of data type to external listeners interested in it. */
private final Map<ListenerDataType, Set<TrackDataListener>> listenerSetsPerType =
new EnumMap<ListenerDataType, Set<TrackDataListener>>(ListenerDataType.class);
public TrackDataListeners() {
// Create sets for all data types at startup.
for (ListenerDataType type : ListenerDataType.values()) {
listenerSetsPerType.put(type, new LinkedHashSet<TrackDataListener>());
}
}
/**
* Registers a listener to send data to.
* It is ok to call this method before {@link TrackDataHub#start}, and in that case
* the data will only be passed to listeners when {@link TrackDataHub#start} is called.
*
* @param listener the listener to register
* @param dataTypes the type of data that the listener is interested in
*/
public ListenerRegistration registerTrackDataListener(final TrackDataListener listener, EnumSet<ListenerDataType> dataTypes) {
Log.d(TAG, "Registered track data listener: " + listener);
if (registeredListeners.containsKey(listener)) {
throw new IllegalStateException("Listener already registered");
}
ListenerRegistration registration = new ListenerRegistration(listener, dataTypes);
registeredListeners.put(listener, registration);
for (ListenerDataType type : dataTypes) {
// This is guaranteed not to be null.
Set<TrackDataListener> typeSet = listenerSetsPerType.get(type);
typeSet.add(listener);
}
return registration;
}
/**
* Unregisters a listener to send data to.
*
* @param listener the listener to unregister
*/
public void unregisterTrackDataListener(TrackDataListener listener) {
Log.d(TAG, "Unregistered track data listener: " + listener);
// Remove and keep the corresponding registration.
ListenerRegistration match = registeredListeners.remove(listener);
if (match == null) {
Log.w(TAG, "Tried to unregister listener which is not registered.");
return;
}
// Remove it from the per-type sets
for (ListenerDataType type : match.types) {
listenerSetsPerType.get(type).remove(listener);
}
}
public ListenerRegistration getRegistration(TrackDataListener listener) {
return registeredListeners.get(listener);
}
public Set<TrackDataListener> getListenersFor(ListenerDataType type) {
return listenerSetsPerType.get(type);
}
public EnumSet<ListenerDataType> getAllRegisteredTypes() {
EnumSet<ListenerDataType> listeners = EnumSet.noneOf(ListenerDataType.class);
for (ListenerRegistration registration : this.registeredListeners.values()) {
listeners.addAll(registration.types);
}
return listeners;
}
public boolean hasListeners() {
return !registeredListeners.isEmpty();
}
public int getNumListeners() {
return registeredListeners.size();
}
}
@@ -0,0 +1,127 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.Constants.TAG;
import android.util.Log;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* Manages register/unregister {@link TrackDataListener} and keeping the state
* for each registered listener.
*
* @author Rodrigo Damazio
*/
public class TrackDataManager {
// Map of listener to its state
private final Map<TrackDataListener, ListenerState>
listenerToStateMap = new HashMap<TrackDataListener, ListenerState>();
// Map of track data type to listeners
private final Map<TrackDataType, Set<TrackDataListener>>
typeToListenersMap = new EnumMap<TrackDataType, Set<TrackDataListener>>(TrackDataType.class);
public TrackDataManager() {
for (TrackDataType trackDataType : TrackDataType.values()) {
typeToListenersMap.put(trackDataType, new LinkedHashSet<TrackDataListener>());
}
}
/**
* Registers a listener.
*
* @param listener the listener
* @param trackDataTypes the track data types the listener is interested
*/
public ListenerState registerListener(
TrackDataListener listener, EnumSet<TrackDataType> trackDataTypes) {
if (listenerToStateMap.containsKey(listener)) {
throw new IllegalStateException("Listener is already registered");
}
ListenerState listenerState = new ListenerState(listener, trackDataTypes);
listenerToStateMap.put(listener, listenerState);
for (TrackDataType trackDataType : trackDataTypes) {
typeToListenersMap.get(trackDataType).add(listener);
}
return listenerState;
}
/**
* Unregisters a listener.
*
* @param listener the listener
*/
public void unregisterListener(TrackDataListener listener) {
ListenerState removed = listenerToStateMap.remove(listener);
if (removed == null) {
Log.w(TAG, "Tried to unregister a listener that is not registered.");
return;
}
// Remove the listener from the typeToListenersMap
for (TrackDataType trackDataType : removed.getTrackDataTypes()) {
typeToListenersMap.get(trackDataType).remove(listener);
}
}
/**
* Gets the number of {@link TrackDataListener}.
*/
public int getNumberOfListeners() {
return listenerToStateMap.size();
}
/**
* Gets the track listener state.
*
* @param listener the listener
*/
public ListenerState getListenerState(TrackDataListener listener) {
return listenerToStateMap.get(listener);
}
/**
* Gets the listeners for a {@link TrackDataType}.
*
* @param type the type
*/
public Set<TrackDataListener> getListeners(TrackDataType type) {
return typeToListenersMap.get(type);
}
/**
* Gets all the registered {@link TrackDataType}.
*/
public EnumSet<TrackDataType> getRegisteredTrackDataTypes() {
EnumSet<TrackDataType> types = EnumSet.noneOf(TrackDataType.class);
for (ListenerState registration : this.listenerToStateMap.values()) {
types.addAll(registration.getTrackDataTypes());
}
return types;
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
/**
* Types of track data.
*
* @author Jimmy Shih
*/
public enum TrackDataType {
SELECTED_TRACK, // selected track changes
TRACKS_TABLE, // tracks table changes
WAYPOINTS_TABLE, // waypoints table changes
TRACK_POINTS_TABLE, // track points table changes
SAMPLED_OUT_TRACK_POINTS, // sampled-out track points changes
LOCATION, // location changes
COMPASS, // compass changes
PREFERENCE; // preference changes
}
@@ -19,12 +19,12 @@ package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.ChartView; import com.google.android.apps.mytracks.ChartView;
import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.MyTracksApplication; import com.google.android.apps.mytracks.MyTracksApplication;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.MyTracksLocation; import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor; import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet; import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub; import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener; import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.DoubleBuffer; import com.google.android.apps.mytracks.stats.DoubleBuffer;
@@ -360,12 +360,12 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private synchronized void resumeTrackDataHub() { private synchronized void resumeTrackDataHub() {
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub(); trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of( trackDataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED, TrackDataType.SELECTED_TRACK,
ListenerDataType.TRACK_UPDATES, TrackDataType.TRACKS_TABLE,
ListenerDataType.WAYPOINT_UPDATES, TrackDataType.WAYPOINTS_TABLE,
ListenerDataType.POINT_UPDATES, TrackDataType.TRACK_POINTS_TABLE,
ListenerDataType.SAMPLED_OUT_POINT_UPDATES, TrackDataType.SAMPLED_OUT_TRACK_POINTS,
ListenerDataType.DISPLAY_PREFERENCES)); TrackDataType.PREFERENCE));
} }
/** /**
@@ -19,11 +19,11 @@ package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.MapOverlay; import com.google.android.apps.mytracks.MapOverlay;
import com.google.android.apps.mytracks.MyTracksApplication; import com.google.android.apps.mytracks.MyTracksApplication;
import com.google.android.apps.mytracks.TrackDetailActivity; import com.google.android.apps.mytracks.TrackDetailActivity;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub; import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener; import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.apps.mytracks.stats.TripStatistics;
@@ -420,11 +420,11 @@ public class MapFragment extends Fragment
private synchronized void resumeTrackDataHub() { private synchronized void resumeTrackDataHub() {
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub(); trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of( trackDataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED, TrackDataType.SELECTED_TRACK,
ListenerDataType.WAYPOINT_UPDATES, TrackDataType.WAYPOINTS_TABLE,
ListenerDataType.POINT_UPDATES, TrackDataType.TRACK_POINTS_TABLE,
ListenerDataType.LOCATION_UPDATES, TrackDataType.LOCATION,
ListenerDataType.COMPASS_UPDATES)); TrackDataType.COMPASS));
} }
/** /**
@@ -17,9 +17,9 @@
package com.google.android.apps.mytracks.fragments; package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.MyTracksApplication; import com.google.android.apps.mytracks.MyTracksApplication;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub; import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener; import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.apps.mytracks.stats.TripStatistics;
@@ -257,10 +257,10 @@ public class StatsFragment extends Fragment implements TrackDataListener {
private synchronized void resumeTrackDataHub() { private synchronized void resumeTrackDataHub() {
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub(); trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of( trackDataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED, TrackDataType.SELECTED_TRACK,
ListenerDataType.TRACK_UPDATES, TrackDataType.TRACKS_TABLE,
ListenerDataType.LOCATION_UPDATES, TrackDataType.LOCATION,
ListenerDataType.DISPLAY_PREFERENCES)); TrackDataType.PREFERENCE));
} }
/** /**
@@ -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;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet; import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointCreationRequest; import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.content.WaypointCreationRequest.WaypointType; 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.SensorManager;
import com.google.android.apps.mytracks.services.sensors.SensorManagerFactory; import com.google.android.apps.mytracks.services.sensors.SensorManagerFactory;
import com.google.android.apps.mytracks.services.tasks.PeriodicTaskExecutor; 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.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
@@ -836,35 +833,17 @@ public class TrackRecordingService extends Service {
int pointId = Integer.parseInt(pointUri.getLastPathSegment()); int pointId = Integer.parseInt(pointUri.getLastPathSegment());
// Update the current track: // Update the current track:
if (lastRecordedLocation != null if (lastRecordedLocation != null && lastRecordedLocation.getLatitude() < 90) {
&& lastRecordedLocation.getLatitude() < 90) { TripStatistics tripStatistics = statsBuilder.getStatistics();
ContentValues values = new ContentValues(); tripStatistics.setStopTime(System.currentTimeMillis());
TripStatistics stats = statsBuilder.getStatistics();
if (recordingTrack.getStartId() < 0) { if (recordingTrack.getStartId() < 0) {
values.put(TracksColumns.STARTID, pointId);
recordingTrack.setStartId(pointId); recordingTrack.setStartId(pointId);
} }
values.put(TracksColumns.STOPID, pointId); recordingTrack.setStopId(pointId);
values.put(TracksColumns.STOPTIME, System.currentTimeMillis()); recordingTrack.setNumberOfPoints(recordingTrack.getNumberOfPoints() + 1);
values.put(TracksColumns.NUMPOINTS, recordingTrack.setTripStatistics(tripStatistics);
recordingTrack.getNumberOfPoints() + 1); providerUtils.updateTrack(recordingTrack);
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);
updateCurrentWaypoint(); updateCurrentWaypoint();
} }
} catch (SQLiteException e) { } catch (SQLiteException e) {
@@ -882,30 +861,14 @@ public class TrackRecordingService extends Service {
private void updateCurrentWaypoint() { private void updateCurrentWaypoint() {
if (currentWaypointId >= 0) { if (currentWaypointId >= 0) {
ContentValues values = new ContentValues(); Waypoint waypoint = providerUtils.getWaypoint(currentWaypointId);
TripStatistics waypointStats = waypointStatsBuilder.getStatistics(); if (waypoint != null) {
values.put(WaypointsColumns.STARTTIME, waypointStats.getStartTime()); waypoint.setLength(length);
values.put(WaypointsColumns.LENGTH, length); waypoint.setDuration(
values.put(WaypointsColumns.DURATION, System.currentTimeMillis() System.currentTimeMillis() - statsBuilder.getStatistics().getStartTime());
- statsBuilder.getStatistics().getStartTime()); waypoint.setTripStatistics(waypointStatsBuilder.getStatistics());
values.put(WaypointsColumns.TOTALDISTANCE, providerUtils.updateWaypoint(waypoint);
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);
} }
} }
@@ -1031,19 +994,14 @@ public class TrackRecordingService extends Service {
isRecording = false; isRecording = false;
Track recordedTrack = providerUtils.getTrack(recordingTrackId); Track recordedTrack = providerUtils.getTrack(recordingTrackId);
if (recordedTrack != null) { if (recordedTrack != null) {
TripStatistics stats = recordedTrack.getTripStatistics(); long lastRecordedLocationId = providerUtils.getLastLocationId(recordingTrackId);
stats.setStopTime(System.currentTimeMillis());
stats.setTotalTime(stats.getStopTime() - stats.getStartTime());
long lastRecordedLocationId =
providerUtils.getLastLocationId(recordingTrackId);
ContentValues values = new ContentValues();
if (lastRecordedLocationId >= 0 && recordedTrack.getStopId() >= 0) { if (lastRecordedLocationId >= 0 && recordedTrack.getStopId() >= 0) {
values.put(TracksColumns.STOPID, lastRecordedLocationId); recordedTrack.setStopId(lastRecordedLocationId);
} }
values.put(TracksColumns.STOPTIME, stats.getStopTime()); TripStatistics tripStatistics = recordedTrack.getTripStatistics();
values.put(TracksColumns.TOTALTIME, stats.getTotalTime()); tripStatistics.setStopTime(System.currentTimeMillis());
getContentResolver().update(TracksColumns.CONTENT_URI, values, tripStatistics.setTotalTime(tripStatistics.getStopTime() - tripStatistics.getStartTime());
"_id=" + recordedTrack.getId(), null); providerUtils.updateTrack(recordedTrack);
} }
showNotification(); showNotification();
long recordedTrackId = recordingTrackId; long recordedTrackId = recordingTrackId;
@@ -1,4 +1,4 @@
#Fri Oct 21 20:06:28 BRST 2011 #Tue Jul 17 10:23:25 PDT 2012
eclipse.preferences.version=1 eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 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.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.source=1.6 org.eclipse.jdt.core.compiler.source=1.6
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false 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=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=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=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=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=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=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_assignment=0
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 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_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 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=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=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_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 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=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=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_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=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_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=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=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=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=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.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_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=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_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_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.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.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=2 org.eclipse.jdt.core.formatter.continuation_indentation=2
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=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.disabling_tag=@formatter\:off
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on 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.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_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_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_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_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=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_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_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=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.join_wrapped_lines=true
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false 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_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.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.lineSplit=100 org.eclipse.jdt.core.formatter.lineSplit=100
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false 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_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.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true 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.char=space
org.eclipse.jdt.core.formatter.tabulation.size=2 org.eclipse.jdt.core.formatter.tabulation.size=2
org.eclipse.jdt.core.formatter.use_on_off_tags=false 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.use_tabs_only_for_leading_indentations=false
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true 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_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_outer_expressions_when_nested=true
org.eclipse.jdt.core.formatter.wrap_prefer_two_fragments=false
@@ -1,8 +1,8 @@
#Thu Jul 21 03:05:33 BRT 2011 #Tue Jul 17 10:22:31 PDT 2012
eclipse.preferences.version=1 eclipse.preferences.version=1
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
formatter_profile=_MyTracks formatter_profile=_My Tracks
formatter_settings_version=12 formatter_settings_version=13
org.eclipse.jdt.ui.ignorelowercasenames=true org.eclipse.jdt.ui.ignorelowercasenames=true
org.eclipse.jdt.ui.importorder=com;android;java;javax;org; org.eclipse.jdt.ui.importorder=com;android;java;javax;org;
org.eclipse.jdt.ui.ondemandthreshold=99 org.eclipse.jdt.ui.ondemandthreshold=99
@@ -13,458 +13,413 @@
* License for the specific language governing permissions and limitations under * License for the specific language governing permissions and limitations under
* the License. * the License.
*/ */
package com.google.android.apps.mytracks.content; package com.google.android.apps.mytracks.content;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.location.Location; import android.location.Location;
import android.location.LocationManager;
import android.net.Uri; import android.net.Uri;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
/** /**
* Utility to access data from the mytracks content provider. * Utilities to access data from the My Tracks content provider.
* *
* @author Rodrigo Damazio * @author Rodrigo Damazio
*/ */
public interface MyTracksProviderUtils { public interface MyTracksProviderUtils {
/** /**
* Authority (first part of URI) for the MyTracks content provider: * The authority (the first part of the URI) for the My Tracks content
* provider.
*/ */
public static final String AUTHORITY = "com.google.android.maps.mytracks"; public static final String AUTHORITY = "com.google.android.maps.mytracks";
/** /**
* Deletes all tracks (including track points) from the provider. * Gets a track cursor. The caller owns the returned cursor and is responsible
*/ * for closing it.
void deleteAllTracks();
/**
* Deletes a track with the given track id.
*
* @param trackId the unique track id
*/
void deleteTrack(long trackId);
/**
* Deletes a way point with the given way point id.
* This will also correct the next statistics way point after the deleted one
* to reflect the deletion.
* The generator is needed to stitch together statistics waypoints.
*
* @param waypointId the unique way point id
* @param descriptionGenerator the class to generate descriptions
*/
void deleteWaypoint(long waypointId,
DescriptionGenerator descriptionGenerator);
/**
* Finds the next statistics waypoint after the given waypoint.
*
* @param waypoint a given waypoint
* @return the next statistics waypoint, or null if none found.
*/
Waypoint getNextStatisticsWaypointAfter(Waypoint waypoint);
/**
* Updates the waypoint in the provider.
*
* @param waypoint
* @return true if successful
*/
boolean updateWaypoint(Waypoint waypoint);
/**
* Finds the first recorded location from the location provider.
* *
* @return the first location, or null if no locations available * @param selection the selection
* @param selectionArgs the selection arguments
* @param sortOrder the sort order
*/ */
Location getFirstLocation(); public Cursor getTracksCursor(String selection, String[] selectionArgs, String sortOrder);
/** /**
* Finds the last recorded location from the location provider. * Gets all the tracks. If no track exists, an empty list is returned.
* * <p>
* @return the last location, or null if no locations available * Note that the returned tracks do not have any track points attached.
*/ */
Location getLastLocation(); public List<Track> getAllTracks();
/** /**
* Finds the first recorded waypoint for a given track from the location * Gets a track by a track id. Returns null if not found.
* provider. * <p>
* This is a special waypoint that holds the stats for current segment. * Note that the returned track doesn't have any track points attached.
* *
* @param trackId the id of the track the waypoint belongs to * @param trackId the track id.
* @return the first waypoint, or null if no waypoints available
*/ */
Waypoint getFirstWaypoint(long trackId); public Track getTrack(long trackId);
/** /**
* Finds the given waypoint from the location provider. * Gets the last track. Returns null if doesn't exist.
*
* @param waypointId
* @return the waypoint, or null if it does not exist
*/ */
Waypoint getWaypoint(long waypointId); public Track getLastTrack();
/** /**
* Finds the last recorded location id from the track points provider. * Gets the last track id. Returns -1L if doesn't exist.
*
* @param trackId find last location on this track
* @return the location id, or -1 if no locations available
*/ */
long getLastLocationId(long trackId); public long getLastTrackId();
/** /**
* Finds the id of the 1st waypoint for a given track. * Returns true if a track exists.
* The 1st waypoint is special as it contains the stats for the current *
* segment. * @param trackId the track id
*
* @param trackId find last location on this track
* @return the waypoint id, or -1 if no waypoints are available
*/ */
long getFirstWaypointId(long trackId); public boolean trackExists(long trackId);
/** /**
* Finds the id of the 1st waypoint for a given track. * Inserts a track.
* The 1st waypoint is special as it contains the stats for the current * <p>
* segment. * Note: This doesn't insert any track points.
* *
* @param trackId find last location on this track * @param track the track
* @return the waypoint id, or -1 if no waypoints are available * @return the content provider URI of the inserted track.
*/ */
long getLastWaypointId(long trackId); public Uri insertTrack(Track track);
/** /**
* Gets the next marker number. * Updates a track.
* * <p>
* Note: This doesn't update any track points.
*
* @param track the track
*/
public void updateTrack(Track track);
/**
* Deletes all tracks (including waypoints and track points).
*/
public void deleteAllTracks();
/**
* Deletes a track.
*
* @param trackId the track id
*/
public void deleteTrack(long trackId);
/**
* Creates a {@link Track} from a cursor.
*
* @param cursor the cursor pointing to the track
*/
public Track createTrack(Cursor cursor);
/**
* Creates a {@link ContentValues} from a track.
*
* @param track the track
*/
public ContentValues createContentValues(Track track);
/**
* Gets a waypoint cursor. The caller owns the returned cursor and is
* responsible for closing it.
*
* @param selection the selection
* @param selectionArgs the selection arguments
* @param sortOrder the sort order
* @param maxWaypoints the maximum number of waypoints to return
*/
public Cursor getWaypointsCursor(
String selection, String[] selectionArgs, String sortOrder, int maxWaypoints);
/**
* Gets a waypoint cursor for a track. The caller owns the returned cursor and
* is responsible for closing it.
*
* @param trackId the track id
* @param minWaypointId the minimum waypoint id
* @param maxWaypoints the maximum number of waypoints to return
*/
public Cursor getWaypointsCursor(long trackId, long minWaypointId, int maxWaypoints);
/**
* Gets the first recorded waypoint for a track. The first waypoint is special
* as it contains the stats for the current segment. Returns null if it
* doesn't exist.
*
* @param trackId the track id
*/
public Waypoint getFirstWaypoint(long trackId);
/**
* Gets the first waypoint id for a track. The first waypoint is special as it
* contains the stats for the current segment. Returns -1L if it doesn't
* exist.
*
* @param trackId the track id
*/
public long getFirstWaypointId(long trackId);
/**
* Gets the last waypoint id for a track. Returns -1L if it doesn't exist.
*
* @param trackId the track id
*/
public long getLastWaypointId(long trackId);
/**
* Gets the next marker number. Returns -1 if not able to get the next marker
* number.
*
* @param trackId the track id * @param trackId the track id
* @param statistics true for statistics marker, false for waypoint marker * @param statistics true for statistics marker, false for waypoint marker
* @return the next number or -1 if unable to get the value
*/ */
int getNextMarkerNumber(long trackId, boolean statistics); public int getNextMarkerNumber(long trackId, boolean statistics);
/**
* Finds the last recorded track from the track provider.
*
* @return the last track, or null if no tracks available
*/
Track getLastTrack();
/** /**
* Finds the last recorded track id from the tracks provider. * Gets the next statistics waypoint after the given waypoint. Returns null if
* * it doesn't exists.
* @return the track id, or -1 if no tracks available *
* @param waypoint the given waypoint
*/ */
long getLastTrackId(); public Waypoint getNextStatisticsWaypointAfter(Waypoint waypoint);
/** /**
* Finds a location by given unique id. * Gets a waypoint from a waypoint id. Returns null if not found.
* *
* @param id the desired id * @param waypointId the waypoint id
* @return a Location object, or null if not found
*/ */
Location getLocation(long id); public Waypoint getWaypoint(long waypointId);
/** /**
* Creates a cursor over the locations in the track points provider which * Inserts a waypoint.
* iterates over a given range of unique ids. *
* Caller owns the returned cursor and is responsible for closing it. * @param waypoint the waypoint
* * @return the content provider URI of the inserted waypoint.
* @param trackId the id of the track for which to get the points
* @param minTrackPointId the minimum id for the track points
* @param maxLocations maximum number of locations retrieved
* @param descending if true the results will be returned in descending id
* order (latest location first)
* @return A cursor over the selected range of locations
*/ */
Cursor getLocationsCursor(long trackId, long minTrackPointId, public Uri insertWaypoint(Waypoint waypoint);
int maxLocations, boolean descending);
/** /**
* Creates a cursor over the waypoints of a track. * Updates a waypoint. Returns true if successful.
* Caller owns the returned cursor and is responsible for closing it. *
* * @param waypoint the waypoint
* @param trackId the id of the track for which to get the points
* @param minWaypointId the minimum id for the track points
* @param maxWaypoints the maximum number of waypoints to return
* @return A cursor over the selected range of locations
*/ */
Cursor getWaypointsCursor(long trackId, long minWaypointId, public boolean updateWaypoint(Waypoint waypoint);
int maxWaypoints);
/** /**
* Creates a cursor over waypoints with the given selection. * Deletes a waypoint. If deleting a statistics waypoint, this will also
* Caller owns the returned cursor and is responsible for closing it. * correct the next statistics waypoint after the deleted one to reflect the
* * deletion. The generator is used to update the next statistics waypoint.
* @param selection a given selection *
* @param selectionArgs arguments for the given selection * @param waypointId the waypoint id
* @param order the order in which to return results * @param descriptionGenerator the description generator
* @param maxWaypoints the maximum number of waypoints to return
* @return a cursor of the selected waypoints
*/ */
Cursor getWaypointsCursor(String selection, String[] selectionArgs, String order, int maxWaypoints); public void deleteWaypoint(long waypointId, DescriptionGenerator descriptionGenerator);
/** /**
* Finds a track by given unique track id. * Creates a waypoint from a cursor.
* Note that the returned track object does not have any track points attached. *
* Use {@link #getLocationIterator(long, long, boolean, LocationFactory)} to load * @param cursor the cursor pointing to the waypoint
* the track points.
*
* @param id desired unique track id
* @return a Track object, or null if not found
*/ */
Track getTrack(long id); public Waypoint createWaypoint(Cursor cursor);
/** /**
* Retrieves all tracks without track points. If no tracks exist, an empty * Gets the first recorded location. Returns null if it doesn't exist.
* list will be returned. Use {@link #getLocationIterator(long, long, boolean, LocationFactory)}
* to load the track points.
*
* @return a list of all the recorded tracks
*/ */
List<Track> getAllTracks(); public Location getFirstLocation();
/** /**
* Creates a cursor over the tracks provider with a given selection. * Gets the last recorded location. Returns null if it doesn't exist.
* Caller owns the returned cursor and is responsible for closing it.
*
* @param selection a given selection
* @param selectionArgs parameters for the given selection
* @param order the order to return results in
* @return a cursor of the selected tracks
*/ */
Cursor getTracksCursor(String selection, String[] selectionArgs, String order); public Location getLastLocation();
/** /**
* Inserts a track in the tracks provider. * Gets a location by track point id. Returns null if not found.
* Note: This does not insert any track points. *
* Use {@link #insertTrackPoint(Location, long)} to insert them. * @param trackPointId the track point id
*
* @param track the track to insert
* @return the content provider URI for the inserted track
*/ */
Uri insertTrack(Track track); public Location getLocation(long trackPointId);
/** /**
* Inserts a track point in the tracks provider. * Creates a location cursor. The caller owns the returned cursor and is
* * responsible for closing it.
* @param location the location to insert *
* @return the content provider URI for the inserted track point * @param trackId the track id
* @param startTrackPointId the starting track point id
* @param maxLocations maximum number of locations to return
* @param descending true to sort the result in descending order (latest
* location first)
*/ */
Uri insertTrackPoint(Location location, long trackId); public Cursor getLocationsCursor(
long trackId, long startTrackPointId, int maxLocations, boolean descending);
/** /**
* Inserts multiple track points in a single operation. * Creates a new read-only iterator over a given track's points. It provides a
* * lightweight way of iterating over long tracks without failing due to the
* @param locations an array of locations to insert * underlying cursor limitations. Since it's a read-only iterator,
* @param length the number of locations (from the beginning of the array) * {@link Iterator#remove()} always throws
* to actually insert, or -1 for all of them * {@link UnsupportedOperationException}. Each call to
* @param trackId the ID of the track to insert the points into * {@link LocationIterator#next()} may advance to the next DB record, and if
* so, the iterator calls {@link LocationFactory#createLocation()} and
* populates it with information retrieved from the record. When done with
* iteration, {@link LocationIterator#close()} must be called.
*
* @param trackId the track id
* @param startTrackPointId the start track point id or -1L to start from the
* first point
* @param descending true to sort the result in descending order (latest
* location first)
* @param locationFactory the location factory
*/
public LocationIterator getLocationIterator(
long trackId, long startTrackPointId, boolean descending, LocationFactory locationFactory);
/**
* Gets the last location id for a track. Returns -1L if it doesn't exist.
*
* @param trackId the track id
*/
public long getLastLocationId(long trackId);
/**
* Inserts a track point.
*
* @param location the location
* @param trackId the track id
* @return the content provider URI of the inserted track point
*/
public Uri insertTrackPoint(Location location, long trackId);
/**
* Inserts multiple track points.
*
* @param locations an array of locations
* @param length the number of locations (from the beginning of the array) to
* insert, or -1 for all of them
* @param trackId the track id
* @return the number of points inserted * @return the number of points inserted
*/ */
int bulkInsertTrackPoints(Location[] locations, int length, long trackId); public int bulkInsertTrackPoints(Location[] locations, int length, long trackId);
/** /**
* Inserts a waypoint in the provider. * Fills a location from a cursor.
* *
* @param waypoint the waypoint to insert * @param cursor the cursor pointing to the location
* @return the content provider URI for the inserted track * @param location the location to be overwritten
*/ */
Uri insertWaypoint(Waypoint waypoint); public void fillLocation(Cursor cursor, Location location);
/** /**
* Tests if a track with given id exists. * Creates a location object from a cursor.
* *
* @param id the unique id * @param cursor the cursor pointing to the location
* @return true if track exists
*/ */
boolean trackExists(long id); public Location createLocation(Cursor cursor);
/** /**
* Updates a track in the content provider. * A lightweight wrapper around the original {@link Cursor} with a method to
* Note: This will not update any track points. * clean up.
*
* @param track a given track
*/ */
void updateTrack(Track track); public interface LocationIterator extends Iterator<Location> {
/**
* Creates a Track object from a given cursor.
*
* @param cursor a cursor pointing at a db or provider with tracks
* @return a new Track object
*/
Track createTrack(Cursor cursor);
/**
* Creates the ContentValues for a given Track object.
*
* Note: If the track has an id<0 the id column will not be filled.
*
* @param track a given track object
* @return a filled in ContentValues object
*/
ContentValues createContentValues(Track track);
/**
* Creates a location object from a given cursor.
*
* @param cursor a cursor pointing at a db or provider with locations
* @return a new location object
*/
Location createLocation(Cursor cursor);
/**
* Fill a location object with values from a given cursor.
*
* @param cursor a cursor pointing at a db or provider with locations
* @param location a location object to be overwritten
*/
void fillLocation(Cursor cursor, Location location);
/**
* Creates a waypoint object from a given cursor.
*
* @param cursor a cursor pointing at a db or provider with waypoints.
* @return a new waypoint object
*/
Waypoint createWaypoint(Cursor cursor);
/**
* A lightweight wrapper around the original {@link Cursor} with a method to clean up.
*/
interface LocationIterator extends Iterator<Location> {
/**
* Returns ID of the most recently retrieved track point through a call to {@link #next()}.
*
* @return the ID of the most recent track point ID.
*/
long getLocationId();
/** /**
* Should be called in case the underlying iterator hasn't reached the last record. * Gets the most recently retrieved track point id by {@link #next()}.
* Calling it if it has reached the last record is a no-op.
*/ */
void close(); public long getLocationId();
/**
* Closes the iterator.
*/
public void close();
} }
/** /**
* A factory for creating new {@class Location}s. * A factory for creating new {@link Location}.
*/ */
interface LocationFactory { public interface LocationFactory {
/** /**
* Creates a new {@link Location} object to be populated from the underlying database record. * Creates a new {@link Location}. An implementation can create new
* It's up to the implementing class to decide whether to create a new instance or reuse * instances or reuse existing instances for optimization.
* existing to optimize for speed.
*
* @return a {@link Location} to be populated from the database.
*/ */
Location createLocation(); public Location createLocation();
} }
/** /**
* The default {@class Location}s factory, which creates a new location of 'gps' type. * The default {@link LocationFactory} which creates a location each time.
*/ */
LocationFactory DEFAULT_LOCATION_FACTORY = new LocationFactory() { public LocationFactory DEFAULT_LOCATION_FACTORY = new LocationFactory() {
@Override @Override
public Location createLocation() { public Location createLocation() {
return new Location("gps"); return new Location(LocationManager.GPS_PROVIDER);
} }
}; };
/** /**
* A location factory which uses two location instances (one for the current location, * A {@link LocationFactory} which uses two location instances (one for the
* and one for the previous), useful when we need to keep the last location. * current location and one for the previous), useful when needing to keep the
* last location.
*/ */
public class DoubleBufferedLocationFactory implements LocationFactory { public class DoubleBufferedLocationFactory implements LocationFactory {
private final Location locs[] = new MyTracksLocation[] {
new MyTracksLocation("gps"), private final Location locations[] = new MyTracksLocation[] {
new MyTracksLocation("gps") new MyTracksLocation(LocationManager.GPS_PROVIDER),
}; new MyTracksLocation(LocationManager.GPS_PROVIDER) };
private int lastLoc = 0; private int lastLocation = 0;
@Override @Override
public Location createLocation() { public Location createLocation() {
lastLoc = (lastLoc + 1) % locs.length; lastLocation = (lastLocation + 1) % locations.length;
return locs[lastLoc]; return locations[lastLocation];
} }
} }
/** /**
* Creates a new read-only iterator over all track points for the given track. It provides * A factory which can produce instances of {@link MyTracksProviderUtils}, and
* a lightweight way of iterating over long tracks without failing due to the underlying cursor * can be overridden for testing.
* limitations. Since it's a read-only iterator, {@link Iterator#remove()} always throws
* {@class UnsupportedOperationException}.
*
* Each call to {@link LocationIterator#next()} may advance to the next DB record, and if so,
* the iterator calls {@link LocationFactory#createLocation()} and populates it with information
* retrieved from the record.
*
* When done with iteration, you must call {@link LocationIterator#close()} to make sure that all
* resources are properly deallocated.
*
* Example use:
* <code>
* ...
* LocationIterator it = providerUtils.getLocationIterator(
* 1, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
* try {
* for (Location loc : it) {
* ... // Do something useful with the location.
* }
* } finally {
* it.close();
* }
* ...
* </code>
*
* @param trackId the ID of a track to retrieve locations for.
* @param startTrackPointId the ID of the first track point to load, or -1 to start from
* the first point.
* @param descending if true the results will be returned in descending ID
* order (latest location first).
* @param locationFactory the factory for creating new locations.
*
* @return the read-only iterator over the given track's points.
*/
LocationIterator getLocationIterator(long trackId, long startTrackPointId, boolean descending,
LocationFactory locationFactory);
/**
* A factory which can produce instances of {@link MyTracksProviderUtils},
* and can be overridden in tests (a.k.a. poor man's guice).
*/ */
public static class Factory { public static class Factory {
private static Factory instance = new Factory(); private static Factory instance = new Factory();
/** /**
* Creates and returns an instance of {@link MyTracksProviderUtils} which * Creates an instance of {@link MyTracksProviderUtils}.
* uses the given context to access its data. *
* @param context the context
*/ */
public static MyTracksProviderUtils get(Context context) { public static MyTracksProviderUtils get(Context context) {
return instance.newForContext(context); return instance.newForContext(context);
} }
/** /**
* Returns the global instance of this factory. * Returns the factory instance.
*/ */
public static Factory getInstance() { public static Factory getInstance() {
return instance; return instance;
} }
/** /**
* Overrides the global instance for this factory, to be used for testing. * Overrides the factory instance for testing. Don't forget to set it back
* If used, don't forget to set it back to the original value after the * to the original value after testing.
* test is run. *
* @param factory the factory
*/ */
public static void overrideInstance(Factory factory) { public static void overrideInstance(Factory factory) {
instance = factory; instance = factory;
} }
/** /**
* Creates an instance of {@link MyTracksProviderUtils}. * Creates an instance of {@link MyTracksProviderUtils}. Allows subclasses
* to override for testing.
*
* @param context the context
*/ */
protected MyTracksProviderUtils newForContext(Context context) { protected MyTracksProviderUtils newForContext(Context context) {
return new MyTracksProviderUtilsImpl(context.getContentResolver()); return new MyTracksProviderUtilsImpl(context.getContentResolver());

Some files were not shown because too many files have changed in this diff Show More