forked from upstream-mirrors/OpenTracks
Refactor code.
TrackWriter = handle writing a track to an outputstream SaveAsyncTask = handle saving to external storage SaveActivity = handle all UI messages when saving to external storage
This commit is contained in:
@@ -15,13 +15,21 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<plurals name="importFiles">
|
||||
<item quantity="zero">@string/import_files_zero</item>
|
||||
<item quantity="one">@string/import_files_one</item>
|
||||
<item quantity="two">@string/import_files_two</item>
|
||||
<item quantity="few">@string/import_files_few</item>
|
||||
<item quantity="many">@string/import_files_many</item>
|
||||
<item quantity="other">@string/import_files_other</item>
|
||||
<plurals name="files">
|
||||
<item quantity="zero">@string/files_zero</item>
|
||||
<item quantity="one">@string/files_one</item>
|
||||
<item quantity="two">@string/files_two</item>
|
||||
<item quantity="few">@string/files_few</item>
|
||||
<item quantity="many">@string/files_many</item>
|
||||
<item quantity="other">@string/files_other</item>
|
||||
</plurals>
|
||||
<plurals name="tracks">
|
||||
<item quantity="zero">@string/tracks_zero</item>
|
||||
<item quantity="one">@string/tracks_one</item>
|
||||
<item quantity="two">@string/tracks_two</item>
|
||||
<item quantity="few">@string/tracks_few</item>
|
||||
<item quantity="many">@string/tracks_many</item>
|
||||
<item quantity="other">@string/tracks_other</item>
|
||||
</plurals>
|
||||
<plurals name="voiceHours">
|
||||
<item quantity="zero">@string/voice_hours_zero</item>
|
||||
|
||||
+210
-148
@@ -16,7 +16,7 @@ limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<!-- About Page -->
|
||||
<!-- About -->
|
||||
<string name="about_copyright_notice"
|
||||
translation_description="In the 'About My Tracks' page, the copyright notice.">
|
||||
Copyright 2009 Google Inc.
|
||||
@@ -556,7 +556,7 @@ limitations under the License.
|
||||
Segment
|
||||
</string>
|
||||
|
||||
<!-- EULA -->
|
||||
<!-- Eula -->
|
||||
<string name="eula_accept"
|
||||
translation_description="In the 'Terms of Service' dialog, the button to accept.">
|
||||
Accept
|
||||
@@ -599,44 +599,60 @@ limitations under the License.
|
||||
</string>
|
||||
|
||||
<!-- External Storage -->
|
||||
<string name="external_storage_canceled"
|
||||
translation_description="When importing/saving to the external storage, the message indicating
|
||||
that the operation was canceled.">
|
||||
The operation was canceled.
|
||||
</string>
|
||||
<string name="external_storage_not_available"
|
||||
translation_description="Error message when the external storage is not available.">
|
||||
External storage not available.
|
||||
</string>
|
||||
<string name="external_storage_not_writeable"
|
||||
translation_description="Error messazge when the external storage is not writeable.">
|
||||
translation_description="Error message when the external storage is not writeable.">
|
||||
External storage not writeable.
|
||||
</string>
|
||||
<string name="external_storage_save_error"
|
||||
translation_description="When saving to the external storage, the error message indicating not
|
||||
able to save.">
|
||||
Unable to save to external storage.
|
||||
|
||||
<!-- Files -->
|
||||
<string name="files_few"
|
||||
translation_description="The phrase for 'few' files. This is used when the language requires
|
||||
special treatment of 'small' numbers. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
|
||||
|
||||
%1$d an integer number. The number is a small number.">
|
||||
%1$d files
|
||||
</string>
|
||||
<string name="external_storage_save_error_create_dir"
|
||||
translation_description="When saving to the external storage, the error message indicating not
|
||||
able to create a directory.">
|
||||
Unable to create a directory on the external storage.
|
||||
<string name="files_many"
|
||||
translation_description="The phrase for 'many' files. This is used when the language requires
|
||||
special treatment of 'large' numbers. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
|
||||
|
||||
%1$d an integer number. The number is a large number.">
|
||||
%1$d files
|
||||
</string>
|
||||
<string name="external_storage_save_error_no_track"
|
||||
translation_description="When saving to the external storage, the error message indicating that
|
||||
no track is saved.">
|
||||
No track is saved to the external storage.
|
||||
<string name="files_one"
|
||||
translation_description="The phrase for 1 file. This is used when the language requires special
|
||||
treatment of numbers like one. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
|
||||
1 file
|
||||
</string>
|
||||
<string name="external_storage_save_progress_message"
|
||||
translation_description="When saving to the external storage, the progress message.">
|
||||
Saving to external storage…
|
||||
<string name="files_other"
|
||||
translation_description="The phrase for 'other' files. This is used when the language does not
|
||||
require special treatment of the given quantity (as with all numbers in Chinese). See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
|
||||
|
||||
%1$d an integer number.">
|
||||
%1$d files
|
||||
</string>
|
||||
<string name="external_storage_save_success"
|
||||
translation_description="When saving to the external storage, the success message">
|
||||
Finished saving to external storage.
|
||||
<string name="files_two"
|
||||
translation_description="The phrase for 2 files. This is used when the language requires special
|
||||
treatment of numbers like two. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
|
||||
2 files
|
||||
</string>
|
||||
<string name="files_zero"
|
||||
translation_description="The phrase for 0 files. This is used when the language requires special
|
||||
treatment of the number 0. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
|
||||
0 files
|
||||
</string>
|
||||
|
||||
<!-- Generic Strings -->
|
||||
<!-- Generic -->
|
||||
<string name="generic_add"
|
||||
translation_description="The text on a button to say add.">
|
||||
Add
|
||||
@@ -703,7 +719,7 @@ limitations under the License.
|
||||
Success
|
||||
</string>
|
||||
|
||||
<!-- GPS -->
|
||||
<!-- Gps -->
|
||||
<string name="gps_google_location_settings"
|
||||
translation_description="In Android, the Google location settings to modify location settings
|
||||
for all Google applications. See Settings -> Google -> Location settings.">
|
||||
@@ -744,7 +760,7 @@ limitations under the License.
|
||||
Waiting for GPS signal.
|
||||
</string>
|
||||
|
||||
<!-- Help page -->
|
||||
<!-- Help -->
|
||||
<string name="help_about"
|
||||
translation_description="In the 'Help' page, the button on the bottom to open the 'About My
|
||||
Tracks' dialog.">
|
||||
@@ -961,47 +977,14 @@ limitations under the License.
|
||||
</string>
|
||||
|
||||
<!-- Import -->
|
||||
<string name="import_files_few"
|
||||
translation_description="When importing files, the phrase for 'few' files. This is used when the
|
||||
language requires special treatment of 'small' numbers. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
|
||||
<string name="import_error"
|
||||
translation_description="When importing from the external storage, the error message indicating
|
||||
that only some of the files are imported.
|
||||
|
||||
%1$d an integer number. The number is a small number.">
|
||||
%1$d files
|
||||
</string>
|
||||
<string name="import_files_many"
|
||||
translation_description="When importing files, the phrase for 'many' files. This is used when
|
||||
the language requires special treatment of 'large' numbers. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
|
||||
|
||||
%1$d an integer number. The number is a large number.">
|
||||
%1$d files
|
||||
</string>
|
||||
<string name="import_files_one"
|
||||
translation_description="When importing files, the phrase for 1 file. This is used when the
|
||||
language requires special treatment of numbers like one. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
|
||||
1 file
|
||||
</string>
|
||||
<string name="import_files_other"
|
||||
translation_description="When importing files, the phrase for 'other' files. This is used when
|
||||
the language does not require special treatment of the given quantity (as with all numbers in
|
||||
Chinese). See http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
|
||||
|
||||
%1$d an integer number.">
|
||||
%1$d files
|
||||
</string>
|
||||
<string name="import_files_two"
|
||||
translation_description="When importing files, the phrase for 2 files. This is used when the
|
||||
language requires special treatment of numbers like two. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
|
||||
2 files
|
||||
</string>
|
||||
<string name="import_files_zero"
|
||||
translation_description="When importing files, the phrase for 0 files. This is used when the
|
||||
language requires special treatment of the number 0. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
|
||||
0 files
|
||||
%1$d the number of files successfully imported. E.g., '2'.
|
||||
%2$s the number of files in the directory. E.g., '2 files'.
|
||||
%3$s the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
|
||||
Imported %1$d of %2$s from %3$s.
|
||||
</string>
|
||||
<string name="import_no_directory"
|
||||
translation_description="When importing from the external storage, the error message when the
|
||||
@@ -1010,13 +993,6 @@ limitations under the License.
|
||||
%1$s: the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
|
||||
%1$s does not exist.
|
||||
</string>
|
||||
<string name="import_no_file"
|
||||
translation_description="When importing from the external storage, the message indicating
|
||||
that no file is found.
|
||||
|
||||
%1$s: the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
|
||||
No file found in %1$s.
|
||||
</string>
|
||||
<string name="import_progress_message"
|
||||
translation_description="When importing from the external storage, the progress message.
|
||||
|
||||
@@ -1026,10 +1002,9 @@ limitations under the License.
|
||||
<string name="import_success"
|
||||
translation_description="When importing from the external storage, the success message.
|
||||
|
||||
%1$d the number of files successfully imported. E.g., '2'.
|
||||
%2$s the number of files in the directory. E.g., '2 files'.
|
||||
%3$s the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
|
||||
Imported %1$d of %2$s from %3$s.
|
||||
%1$s the number of files. E.g., '2 files'.
|
||||
%2$s the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
|
||||
Imported %1$s from %2$s.
|
||||
</string>
|
||||
|
||||
<!-- Maps List -->
|
||||
@@ -1079,27 +1054,6 @@ limitations under the License.
|
||||
user that the marker will be permanently deleted from the device.">
|
||||
The marker will be permanently deleted from the device.
|
||||
</string>
|
||||
<string name="marker_detail_statistics_title"
|
||||
translation_description="In the marker detail page, the title for the statistics section
|
||||
indicating that the data below are since the last split marker.">
|
||||
Since last split marker:
|
||||
</string>
|
||||
<string name="marker_detail_waypoint_marker_type"
|
||||
translation_description="In the marker detail page, the label for the marker type.
|
||||
|
||||
%1$s marker type. E.g., 'building'.">
|
||||
Marker type: %1$s
|
||||
</string>
|
||||
<string name="marker_edit_marker_type"
|
||||
translation_description="In the marker edit page, the label for the marker type, including an
|
||||
example.">
|
||||
Marker type (e.g. waterfall)
|
||||
</string>
|
||||
<string name="marker_edit_marker_type_hint"
|
||||
translation_description="In the marker edit page, the hint in the marker type text field when it
|
||||
is empty.">
|
||||
Marker type
|
||||
</string>
|
||||
<string name="marker_label_end"
|
||||
translation_description="When inserting a marker to indicate the end of a track, the marker's
|
||||
name. E.g., 'track_name (End)'.
|
||||
@@ -1114,10 +1068,6 @@ limitations under the License.
|
||||
%1$s track name. E.g., 'track_name'.">
|
||||
%1$s (Start)
|
||||
</string>
|
||||
<string name="marker_list_empty_message"
|
||||
translation_description="The message when the marker list is empty.">
|
||||
You don\'t have any markers.
|
||||
</string>
|
||||
<string name="marker_name_format"
|
||||
translation_description="When adding a marker, the string format for the default name.
|
||||
|
||||
@@ -1131,7 +1081,38 @@ limitations under the License.
|
||||
Split %1$d
|
||||
</string>
|
||||
|
||||
<!-- Menu Items -->
|
||||
<!-- Marker Detail -->
|
||||
<string name="marker_detail_statistics_title"
|
||||
translation_description="In the marker detail page, the title for the statistics section
|
||||
indicating that the data below are since the last split marker.">
|
||||
Since last split marker:
|
||||
</string>
|
||||
<string name="marker_detail_waypoint_marker_type"
|
||||
translation_description="In the marker detail page, the label for the marker type.
|
||||
|
||||
%1$s marker type. E.g., 'building'.">
|
||||
Marker type: %1$s
|
||||
</string>
|
||||
|
||||
<!-- Marker Edit -->
|
||||
<string name="marker_edit_marker_type"
|
||||
translation_description="In the marker edit page, the label for the marker type, including an
|
||||
example.">
|
||||
Marker type (e.g. waterfall)
|
||||
</string>
|
||||
<string name="marker_edit_marker_type_hint"
|
||||
translation_description="In the marker edit page, the hint in the marker type text field when it
|
||||
is empty.">
|
||||
Marker type
|
||||
</string>
|
||||
|
||||
<!-- Marker List -->
|
||||
<string name="marker_list_empty_message"
|
||||
translation_description="The message when the marker list is empty.">
|
||||
You don\'t have any markers.
|
||||
</string>
|
||||
|
||||
<!-- Menu -->
|
||||
<string name="menu_aggregated_statistics"
|
||||
translation_description="In the menu, to go to the aggregated statistics page.
|
||||
|
||||
@@ -1373,14 +1354,28 @@ limitations under the License.
|
||||
write data to Google My Tracks and start/stop Google My Tracks recording
|
||||
</string>
|
||||
|
||||
<!-- Restore -->
|
||||
<string name="restore_error"
|
||||
translation_description="When restoring from backup, the error message.">
|
||||
Unable to restore from backup.
|
||||
<!-- Save -->
|
||||
<string name="save_error"
|
||||
translation_description="When saving to the external storage, the error message indicating that
|
||||
only some of the tracks are saved.
|
||||
|
||||
%1$d the number of tracks successfully saved. E.g., '1'.
|
||||
%1$s the number of tracks to save. E.g., '2 tracks'.
|
||||
%2$s the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
|
||||
Saved %1$d of %2$s to %3$s.
|
||||
</string>
|
||||
<string name="restore_success"
|
||||
translation_description="When restoring from backup, the success message.">
|
||||
Finished restoring from backup.
|
||||
<string name="save_progress_message"
|
||||
translation_description="When saving to the external storage, the progress message.
|
||||
|
||||
%1$s: the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
|
||||
Saving to %1$s…
|
||||
</string>
|
||||
<string name="save_success"
|
||||
translation_description="When saving to the external storage, the success message.
|
||||
|
||||
%1$s the number of tracks. E.g., '2 tracks'.
|
||||
%2$s the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
|
||||
Saved %1$s to %2$s.
|
||||
</string>
|
||||
|
||||
<!-- Search -->
|
||||
@@ -1586,6 +1581,25 @@ limitations under the License.
|
||||
Sending
|
||||
</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_not_while_recording"
|
||||
translation_description="In settings, the summary for an option that is not available during a
|
||||
track recording.">
|
||||
Not available while recording a track.
|
||||
</string>
|
||||
<string name="settings_show"
|
||||
translation_description="In settings, the section label to show additional values.
|
||||
|
||||
Character limit: ~26 (1 line)">
|
||||
Show
|
||||
</string>
|
||||
<string name="settings_switch_account"
|
||||
translation_description="In the 'Settings' page, the option to switch account.
|
||||
|
||||
Character limit: ~26 (1 line)">
|
||||
Switch account
|
||||
</string>
|
||||
|
||||
<!-- Settings Backup -->
|
||||
<string name="settings_backup"
|
||||
translation_description="In the 'Settings' page, the option to change the data backup related
|
||||
@@ -1594,6 +1608,8 @@ limitations under the License.
|
||||
Character limit: ~26 (1 line)">
|
||||
Backup
|
||||
</string>
|
||||
|
||||
<!-- Settings Backup Now -->
|
||||
<string name="settings_backup_now"
|
||||
translation_description="In the 'Backup' settings, the option to backup to the external storage
|
||||
now.
|
||||
@@ -1601,6 +1617,10 @@ limitations under the License.
|
||||
Character limit: ~26 (1 line)">
|
||||
Backup now
|
||||
</string>
|
||||
<string name="settings_backup_now_error"
|
||||
translation_description="When backing up to the external storage, the error messag.">
|
||||
Unable to backup.
|
||||
</string>
|
||||
<string name="settings_backup_now_progress_message"
|
||||
translation_description="In the 'Backup' settings, after pressing the 'Backup now' option, the
|
||||
progress message telling the user that it is writing all tracks and settings to the external
|
||||
@@ -1612,6 +1632,12 @@ limitations under the License.
|
||||
explaining that all tracks and settings will be written to the external storage.">
|
||||
Write a full copy of all your tracks and settings to the external storage.
|
||||
</string>
|
||||
<string name="settings_backup_now_success"
|
||||
translation_description="When backing up to the external storage, the success message">
|
||||
Finished backup.
|
||||
</string>
|
||||
|
||||
<!-- Settings Backup Restore -->
|
||||
<string name="settings_backup_restore"
|
||||
translation_description="In the 'Backup' settings, the option to restore from the external
|
||||
storage.
|
||||
@@ -1626,6 +1652,10 @@ limitations under the License.
|
||||
Restoring a backup from the external storage will overwrite all your current tracks and
|
||||
settings.
|
||||
</string>
|
||||
<string name="settings_backup_restore_error"
|
||||
translation_description="When restoring from backup, the error message.">
|
||||
Unable to restore from backup.
|
||||
</string>
|
||||
<string name="settings_backup_restore_no_backup"
|
||||
translation_description="In the 'Backup' settings, after pressing the 'Restore now' option, the
|
||||
message in the alert dialog informing the user that there is no backup to restore from.">
|
||||
@@ -1647,6 +1677,10 @@ limitations under the License.
|
||||
explaining that it restores all tracks and settings from the external storage.">
|
||||
Restore all tracks and settings from the external storage.
|
||||
</string>
|
||||
<string name="settings_backup_restore_success"
|
||||
translation_description="When restoring from backup, the success message.">
|
||||
Finished restoring from backup.
|
||||
</string>
|
||||
|
||||
<!-- Settings Chart -->
|
||||
<string name="settings_chart_by_distance"
|
||||
@@ -1731,13 +1765,6 @@ limitations under the License.
|
||||
The way to color a track based on slow, medium, and fast speed.
|
||||
</string>
|
||||
|
||||
<!-- Settings Not While Recording -->
|
||||
<string name="settings_not_while_recording"
|
||||
translation_description="In settings, the summary for an option that is not available during a
|
||||
track recording.">
|
||||
Not available while recording a track.
|
||||
</string>
|
||||
|
||||
<!-- Settings Recording -->
|
||||
<string name="settings_recording"
|
||||
translation_description="In the 'Settings' page, the option to change the track recording
|
||||
@@ -2093,14 +2120,6 @@ limitations under the License.
|
||||
New maps created in Google Maps will be public, instead of unlisted. See %1$s for more info.
|
||||
</string>
|
||||
|
||||
<!-- Settings Show -->
|
||||
<string name="settings_show"
|
||||
translation_description="In settings, the section label to show additional values.
|
||||
|
||||
Character limit: ~26 (1 line)">
|
||||
Show
|
||||
</string>
|
||||
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate"
|
||||
translation_description="In the 'Stats settings, the option to show latitude and longitude.
|
||||
@@ -2140,14 +2159,6 @@ limitations under the License.
|
||||
Preferred units
|
||||
</string>
|
||||
|
||||
<!-- Settings Switch Account -->
|
||||
<string name="settings_switch_account"
|
||||
translation_description="In the 'Settings' page, the option to switch account.
|
||||
|
||||
Character limit: ~26 (1 line)">
|
||||
Switch account
|
||||
</string>
|
||||
|
||||
<!-- Share Track -->
|
||||
<string name="share_track_confirm_message"
|
||||
translation_description="The confirmation message when sharing a track with friends.
|
||||
@@ -2193,7 +2204,7 @@ limitations under the License.
|
||||
I would like to share a track with you
|
||||
</string>
|
||||
|
||||
<!-- Stats Page -->
|
||||
<!-- Stats -->
|
||||
<string name="stats_average_moving_pace"
|
||||
translation_description="In the 'Stats' page, the average moving pace.">
|
||||
Avg moving pace
|
||||
@@ -2278,6 +2289,19 @@ limitations under the License.
|
||||
</string>
|
||||
|
||||
<!-- Track -->
|
||||
<string name="track_name_format"
|
||||
translation_description="When creating a new track, the string format to name the track.
|
||||
|
||||
%1$d: track number. E.g., '1'.">
|
||||
Track %1$d
|
||||
</string>
|
||||
<string name="track_record_notification"
|
||||
translation_description="When recording a track, the message on the My Tracks notification
|
||||
informing the user that My Tracks is actively recording his track.">
|
||||
Recording your track…
|
||||
</string>
|
||||
|
||||
<!-- Track Detail -->
|
||||
<string name="track_detail_chart_tab"
|
||||
translation_description="In the track detail page, the title of the tab showing the chart.">
|
||||
Chart
|
||||
@@ -2310,6 +2334,8 @@ limitations under the License.
|
||||
translation_description="In the track detail page, the title of the tab showing the stats.">
|
||||
Stats
|
||||
</string>
|
||||
|
||||
<!-- Track Edit -->
|
||||
<string name="track_edit_activity_type"
|
||||
translation_description="In the page to edit a track, the label for the text field to edit the
|
||||
activity type, including an example.">
|
||||
@@ -2324,6 +2350,8 @@ limitations under the License.
|
||||
translation_description="The title of the track edit page when saving a new track.">
|
||||
New Track
|
||||
</string>
|
||||
|
||||
<!-- Track List -->
|
||||
<string name="track_list_delete_all_confirm_message"
|
||||
translation_description="When deleting all the tracks, the confirmation dialog message informing
|
||||
the user that all tracks and markers will be permanently deleted from the device.">
|
||||
@@ -2344,17 +2372,6 @@ limitations under the License.
|
||||
success.">
|
||||
Now recording a new track.
|
||||
</string>
|
||||
<string name="track_name_format"
|
||||
translation_description="When creating a new track, the string format to name the track.
|
||||
|
||||
%1$d: track number. E.g., '1'.">
|
||||
Track %1$d
|
||||
</string>
|
||||
<string name="track_record_notification"
|
||||
translation_description="When recording a track, the message on the My Tracks notification
|
||||
informing the user that My Tracks is actively recording his track.">
|
||||
Recording your track…
|
||||
</string>
|
||||
|
||||
<!-- Track Widget -->
|
||||
<string name="track_widget_config_title"
|
||||
@@ -2362,6 +2379,50 @@ limitations under the License.
|
||||
Configure Widget Items
|
||||
</string>
|
||||
|
||||
<!-- Tracks -->
|
||||
<string name="tracks_few"
|
||||
translation_description="The phrase for 'few' tracks. This is used when the language requires
|
||||
special treatment of 'small' numbers. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
|
||||
|
||||
%1$d an integer number. The number is a small number.">
|
||||
%1$d tracks
|
||||
</string>
|
||||
<string name="tracks_many"
|
||||
translation_description="The phrase for 'many' tracks. This is used when the language requires
|
||||
special treatment of 'large' numbers. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
|
||||
|
||||
%1$d an integer number. The number is a large number.">
|
||||
%1$d tracks
|
||||
</string>
|
||||
<string name="tracks_one"
|
||||
translation_description="The phrase for 1 track. This is used when the language requires special
|
||||
treatment of numbers like one. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
|
||||
1 track
|
||||
</string>
|
||||
<string name="tracks_other"
|
||||
translation_description="The phrase for 'other' tracks. This is used when the language does not
|
||||
require special treatment of the given quantity (as with all numbers in Chinese). See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
|
||||
|
||||
%1$d an integer number.">
|
||||
%1$d tracks
|
||||
</string>
|
||||
<string name="tracks_two"
|
||||
translation_description="The phrase for 2 tracks. This is used when the language requires
|
||||
special treatment of numbers like two. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
|
||||
2 tracks
|
||||
</string>
|
||||
<string name="tracks_zero"
|
||||
translation_description="The phrase for 0 tracks. This is used when the language requires
|
||||
special treatment of the number 0. See
|
||||
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
|
||||
0 tracks
|
||||
</string>
|
||||
|
||||
<!-- Unit -->
|
||||
<string name="unit_feet"
|
||||
translation_description="Abbreviation for feet. E.g., '2 ft'.">
|
||||
@@ -2395,6 +2456,7 @@ limitations under the License.
|
||||
translation_description="Abbreviation for minutes per mile. E.g, '5 min/mi'.">
|
||||
min/mi
|
||||
</string>
|
||||
|
||||
<!-- Value -->
|
||||
<string name="value_adapt_accuracy"
|
||||
translation_description="In a setting picker, the value to adapt for accuracy.">
|
||||
|
||||
@@ -56,7 +56,7 @@ public class BackupAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
this.externalFileBackup = new ExternalFileBackup(backupActivity);
|
||||
success = false;
|
||||
completed = false;
|
||||
messageId = R.string.external_storage_save_error;
|
||||
messageId = R.string.settings_backup_now_error;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,13 +86,13 @@ public class BackupAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
}
|
||||
|
||||
if (!externalFileBackup.isBackupsDirectoryAvailable(true)) {
|
||||
messageId = R.string.external_storage_save_error_create_dir;
|
||||
messageId = R.string.external_storage_not_writeable;
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
externalFileBackup.writeToDefaultFile();
|
||||
messageId = R.string.external_storage_save_success;
|
||||
messageId = R.string.settings_backup_now_success;
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "IO exception", e);
|
||||
|
||||
@@ -59,7 +59,7 @@ public class RestoreAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
this.externalFileBackup = new ExternalFileBackup(restoreActivity);
|
||||
success = false;
|
||||
completed = false;
|
||||
messageId = R.string.restore_error;
|
||||
messageId = R.string.settings_backup_restore_error;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,7 +85,7 @@ public class RestoreAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
try {
|
||||
externalFileBackup.restoreFromDate(date);
|
||||
messageId = R.string.restore_success;
|
||||
messageId = R.string.settings_backup_restore_success;
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "IO exception", e);
|
||||
|
||||
@@ -61,7 +61,7 @@ public class ImportActivity extends Activity {
|
||||
// the path on the external storage to import
|
||||
private String path;
|
||||
|
||||
// the number of successfully imported files
|
||||
// the number of files successfully imported
|
||||
private int successCount;
|
||||
|
||||
// the number of files to import
|
||||
@@ -137,29 +137,35 @@ public class ImportActivity extends Activity {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
importAsyncTask.cancel(true);
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
}, path);
|
||||
return progressDialog;
|
||||
case DIALOG_RESULT_ID:
|
||||
final boolean success;
|
||||
String message;
|
||||
if (totalCount == 0) {
|
||||
message = getString(R.string.import_no_file, path);
|
||||
String totalFiles = getResources()
|
||||
.getQuantityString(R.plurals.files, totalCount, totalCount);
|
||||
if (successCount == totalCount && totalCount > 0) {
|
||||
success = true;
|
||||
message = getString(R.string.import_success, totalFiles, path);
|
||||
} else {
|
||||
String totalFiles = getResources()
|
||||
.getQuantityString(R.plurals.importFiles, totalCount, totalCount);
|
||||
message = getString(R.string.import_success, successCount, totalFiles, path);
|
||||
success = false;
|
||||
message = getString(R.string.import_error, successCount, totalFiles, path);
|
||||
}
|
||||
return new AlertDialog.Builder(this).setCancelable(true)
|
||||
return new AlertDialog.Builder(this).setCancelable(true).setIcon(
|
||||
success ? android.R.drawable.ic_dialog_info : android.R.drawable.ic_dialog_alert)
|
||||
.setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (!importAll && trackId != -1L) {
|
||||
if (success && !importAll && trackId != -1L) {
|
||||
Intent intent = IntentUtils.newIntent(
|
||||
ImportActivity.this, TrackDetailActivity.class)
|
||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
||||
@@ -167,9 +173,11 @@ public class ImportActivity extends Activity {
|
||||
taskStackBuilder.addNextIntent(intent);
|
||||
taskStackBuilder.startActivities();
|
||||
}
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
}).create();
|
||||
}).setTitle(success ? R.string.generic_success_title : R.string.generic_error_title)
|
||||
.create();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -179,7 +187,7 @@ public class ImportActivity extends Activity {
|
||||
* Invokes when the associated AsyncTask completes.
|
||||
*
|
||||
* @param aSuccessCount the number of files successfully imported
|
||||
* @param aTotalCount the total number of files to import
|
||||
* @param aTotalCount the number of files to import
|
||||
* @param aTrackId the last successfully imported track id
|
||||
*/
|
||||
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount, long aTrackId) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
@@ -42,6 +43,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
private final boolean importAll;
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
private final String path;
|
||||
private final Context context;
|
||||
private WakeLock wakeLock;
|
||||
|
||||
// true if the AsyncTask has completed
|
||||
@@ -70,7 +72,8 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
this.importAll = importAll;
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
this.path = path;
|
||||
|
||||
context = importActivity.getApplicationContext();
|
||||
|
||||
// Get the wake lock if not recording or paused
|
||||
if (PreferencesUtils.getLong(importActivity, R.string.recording_track_id_key)
|
||||
== PreferencesUtils.RECORDING_TRACK_ID_DEFAULT || PreferencesUtils.getBoolean(
|
||||
@@ -154,9 +157,8 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
*/
|
||||
private boolean importFile(final File file) {
|
||||
try {
|
||||
AbstractImporter importer = trackFileFormat == TrackFileFormat.KML ? new KmlImporter(
|
||||
importActivity)
|
||||
: new GpxImporter(importActivity);
|
||||
AbstractImporter importer = trackFileFormat == TrackFileFormat.KML ? new KmlImporter(context)
|
||||
: new GpxImporter(context);
|
||||
long trackIds[] = importer.importFile(new FileInputStream(file));
|
||||
int length = trackIds.length;
|
||||
if (length > 0) {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import com.google.android.apps.mytracks.util.DialogUtils;
|
||||
import com.google.android.apps.mytracks.util.FileUtils;
|
||||
import com.google.android.apps.mytracks.util.IntentUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
@@ -28,12 +29,13 @@ import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* An activity for saving tracks to the SD card. If saving a specific track,
|
||||
* option to save it to a temp directory and play the track afterward.
|
||||
* An activity for saving tracks to the external storage. If saving a specific
|
||||
* track, option to save it to a temp directory and play the track afterward.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
@@ -57,17 +59,18 @@ public class SaveActivity extends Activity {
|
||||
private TrackFileFormat trackFileFormat;
|
||||
private long trackId;
|
||||
private boolean playTrack;
|
||||
private String directoryName;
|
||||
|
||||
private SaveAsyncTask saveAsyncTask;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
// result from the AsyncTask
|
||||
private boolean success;
|
||||
|
||||
// message id from the AsyncTask
|
||||
private int messageId;
|
||||
|
||||
// saved file path from the AsyncTask
|
||||
// the number of tracks successfully saved
|
||||
private int successCount;
|
||||
|
||||
// the number of tracks to save
|
||||
private int totalCount;
|
||||
|
||||
// the last successfully saved path
|
||||
private String savedPath;
|
||||
|
||||
@Override
|
||||
@@ -79,12 +82,29 @@ public class SaveActivity extends Activity {
|
||||
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||
playTrack = intent.getBooleanExtra(EXTRA_PLAY_TRACK, false);
|
||||
|
||||
if (!FileUtils.isExternalStorageWriteable()) {
|
||||
Toast.makeText(this, R.string.external_storage_not_writeable, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
directoryName = playTrack ? FileUtils.buildExternalDirectoryPath(
|
||||
trackFileFormat.getExtension(), "tmp")
|
||||
: FileUtils.buildExternalDirectoryPath(trackFileFormat.getExtension());
|
||||
|
||||
File directory = new File(directoryName);
|
||||
if (!FileUtils.ensureDirectoryExists(directory)) {
|
||||
Toast.makeText(this, R.string.external_storage_not_writeable, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Object retained = getLastNonConfigurationInstance();
|
||||
if (retained instanceof SaveAsyncTask) {
|
||||
saveAsyncTask = (SaveAsyncTask) retained;
|
||||
saveAsyncTask.setActivity(this);
|
||||
} else {
|
||||
saveAsyncTask = new SaveAsyncTask(this, trackFileFormat, trackId, playTrack);
|
||||
saveAsyncTask = new SaveAsyncTask(this, trackFileFormat, trackId, directory);
|
||||
saveAsyncTask.execute();
|
||||
}
|
||||
}
|
||||
@@ -99,37 +119,43 @@ public class SaveActivity extends Activity {
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case DIALOG_PROGRESS_ID:
|
||||
progressDialog = DialogUtils.createHorizontalProgressDialog(this,
|
||||
R.string.external_storage_save_progress_message,
|
||||
new DialogInterface.OnCancelListener() {
|
||||
progressDialog = DialogUtils.createHorizontalProgressDialog(
|
||||
this, R.string.save_progress_message, new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
saveAsyncTask.cancel(true);
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}, directoryName);
|
||||
return progressDialog;
|
||||
case DIALOG_RESULT_ID:
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this)
|
||||
.setCancelable(true)
|
||||
.setIcon(success
|
||||
? android.R.drawable.ic_dialog_info : android.R.drawable.ic_dialog_alert)
|
||||
.setMessage(messageId)
|
||||
.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
boolean success;
|
||||
String message;
|
||||
String totalTracks = getResources()
|
||||
.getQuantityString(R.plurals.tracks, totalCount, totalCount);
|
||||
if (successCount == totalCount && totalCount > 0) {
|
||||
success = true;
|
||||
message = getString(R.string.save_success, totalTracks, directoryName);
|
||||
} else {
|
||||
success = false;
|
||||
message = getString(R.string.save_error, successCount, totalTracks, directoryName);
|
||||
}
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this).setCancelable(true).setIcon(
|
||||
success ? android.R.drawable.ic_dialog_info : android.R.drawable.ic_dialog_alert)
|
||||
.setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int arg1) {
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setTitle(success ? R.string.generic_success_title : R.string.generic_error_title);
|
||||
}).setTitle(success ? R.string.generic_success_title : R.string.generic_error_title);
|
||||
|
||||
if (success && trackId != -1L && !playTrack && savedPath != null) {
|
||||
builder.setNegativeButton(
|
||||
@@ -152,19 +178,19 @@ public class SaveActivity extends Activity {
|
||||
|
||||
/**
|
||||
* Invokes when the associated AsyncTask completes.
|
||||
*
|
||||
* @param isSuccess true if the AsyncTask is successful
|
||||
* @param aMessageId the id of the AsyncTask message
|
||||
* @param aSavedPath the path of the saved file
|
||||
*
|
||||
* @param aSuccessCount the number of tracks successfully saved
|
||||
* @param aTotalCount the number of tracks to save
|
||||
* @param aSavedPath the last successfully saved path
|
||||
*/
|
||||
public void onAsyncTaskCompleted(boolean isSuccess, int aMessageId, String aSavedPath) {
|
||||
this.success = isSuccess;
|
||||
this.messageId = aMessageId;
|
||||
this.savedPath = aSavedPath;
|
||||
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount, String aSavedPath) {
|
||||
successCount = aSuccessCount;
|
||||
totalCount = aTotalCount;
|
||||
savedPath = aSavedPath;
|
||||
removeDialog(DIALOG_PROGRESS_ID);
|
||||
if (success && playTrack && savedPath != null) {
|
||||
Intent intent = new Intent()
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
if (successCount == 1 && totalCount == 1 && playTrack && savedPath != null) {
|
||||
Intent intent = new Intent().addFlags(
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(GOOGLE_EARTH_TOUR_FEATURE_ID, KmlTrackWriter.TOUR_FEATURE_ID)
|
||||
.setClassName(GOOGLE_EARTH_PACKAGE, GOOGLE_EARTH_CLASS)
|
||||
.setDataAndType(Uri.fromFile(new File(savedPath)), GOOGLE_EARTH_KML_MIME_TYPE);
|
||||
@@ -184,7 +210,7 @@ public class SaveActivity extends Activity {
|
||||
|
||||
/**
|
||||
* Sets the progress dialog value.
|
||||
*
|
||||
*
|
||||
* @param number the number of points saved
|
||||
* @param max the maximum number of points
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||
import com.google.android.apps.mytracks.util.FileUtils;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
@@ -30,9 +31,12 @@ import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Async Task to save tracks to the SD card.
|
||||
* Async Task to save tracks to the external storage.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
@@ -43,42 +47,41 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
private SaveActivity saveActivity;
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
private final long trackId;
|
||||
private final boolean useTempDir;
|
||||
|
||||
private final File directory;
|
||||
private final Context context;
|
||||
private final MyTracksProviderUtils myTracksProviderUtils;
|
||||
|
||||
private WakeLock wakeLock;
|
||||
private TrackWriter trackWriter;
|
||||
|
||||
// true if the AsyncTask result is success
|
||||
private boolean success;
|
||||
|
||||
// true if the AsyncTask has completed
|
||||
private boolean completed;
|
||||
|
||||
// message id to return to the activity
|
||||
private int messageId;
|
||||
// the number of tracks successfully saved
|
||||
private int successCount;
|
||||
|
||||
// saved file path to return to the activity
|
||||
// the number of tracks to save
|
||||
private int totalCount;
|
||||
|
||||
// the last successfully saved path
|
||||
private String savedPath;
|
||||
|
||||
/**
|
||||
* Creates an AsyncTask.
|
||||
*
|
||||
* @param saveActivity the activity currently associated with this task.
|
||||
* @param trackFileFormat the track format
|
||||
* @param trackId the track id
|
||||
* @param useTempDir true to use the temp directory
|
||||
* @param saveActivity the activity currently associated with this task
|
||||
* @track id the track id to save, -1L to save all tracks
|
||||
* @param trackFileFormat the track file format
|
||||
* @param directory the directory to save to
|
||||
*/
|
||||
public SaveAsyncTask(SaveActivity saveActivity, TrackFileFormat trackFileFormat, long trackId,
|
||||
boolean useTempDir) {
|
||||
public SaveAsyncTask(
|
||||
SaveActivity saveActivity, TrackFileFormat trackFileFormat, long trackId, File directory) {
|
||||
this.saveActivity = saveActivity;
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
this.trackId = trackId;
|
||||
this.useTempDir = useTempDir;
|
||||
this.directory = directory;
|
||||
context = saveActivity.getApplicationContext();
|
||||
|
||||
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(saveActivity);
|
||||
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
|
||||
|
||||
// Get the wake lock if not recording or paused
|
||||
if (PreferencesUtils.getLong(saveActivity, R.string.recording_track_id_key)
|
||||
@@ -86,11 +89,10 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT)) {
|
||||
wakeLock = SystemUtils.acquireWakeLock(saveActivity, wakeLock);
|
||||
}
|
||||
|
||||
trackWriter = null;
|
||||
success = false;
|
||||
|
||||
completed = false;
|
||||
messageId = R.string.external_storage_save_error;
|
||||
successCount = 0;
|
||||
totalCount = 0;
|
||||
savedPath = null;
|
||||
}
|
||||
|
||||
@@ -102,7 +104,7 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
public void setActivity(SaveActivity saveActivity) {
|
||||
this.saveActivity = saveActivity;
|
||||
if (completed && saveActivity != null) {
|
||||
saveActivity.onAsyncTaskCompleted(success, messageId, savedPath);
|
||||
saveActivity.onAsyncTaskCompleted(successCount, totalCount, savedPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +119,13 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
try {
|
||||
if (trackId != -1L) {
|
||||
return saveOneTrack(trackId, true);
|
||||
totalCount = 1;
|
||||
if (saveOneTrack(trackId)) {
|
||||
successCount = 1;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return saveAllTracks();
|
||||
}
|
||||
@@ -129,35 +137,79 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values) {
|
||||
if (saveActivity != null) {
|
||||
saveActivity.setProgressDialogValue(values[0], values[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
completed = true;
|
||||
if (saveActivity != null) {
|
||||
saveActivity.onAsyncTaskCompleted(successCount, totalCount, savedPath);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
if (trackWriter != null) {
|
||||
trackWriter.stopWriteTrack();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves one track.
|
||||
*
|
||||
* @param id the track id
|
||||
* @param updateSavingProgress true to update the saving progress
|
||||
*/
|
||||
private Boolean saveOneTrack(long id, final boolean updateSavingProgress) {
|
||||
trackWriter = TrackWriterFactory.newWriter(
|
||||
context, myTracksProviderUtils, id, trackFileFormat);
|
||||
if (trackWriter == null) {
|
||||
Log.e(TAG, "Track writer is null");
|
||||
private Boolean saveOneTrack(long id) {
|
||||
Track track = myTracksProviderUtils.getTrack(id);
|
||||
if (track == null) {
|
||||
Log.d(TAG, "No track for " + id);
|
||||
return false;
|
||||
}
|
||||
if (useTempDir) {
|
||||
String dirName = FileUtils.buildExternalDirectoryPath(trackFileFormat.getExtension(), "tmp");
|
||||
trackWriter.setDirectory(new File(dirName));
|
||||
|
||||
// Make sure the file doesn't exist yet (possibly by changing the filename)
|
||||
String fileName = FileUtils.buildUniqueFileName(
|
||||
directory, track.getName(), trackFileFormat.getExtension());
|
||||
if (fileName == null) {
|
||||
Log.d(TAG, "Unable to get a unique filename for " + track.getName());
|
||||
return false;
|
||||
}
|
||||
trackWriter.setOnWriteListener(new TrackWriter.OnWriteListener() {
|
||||
@Override
|
||||
public void onWrite(int number, int max) {
|
||||
// Update the progress dialog once every 500 points
|
||||
if (updateSavingProgress && number % 500 == 0) {
|
||||
publishProgress(number, max);
|
||||
}
|
||||
|
||||
trackWriter = new TrackWriter(
|
||||
context, myTracksProviderUtils, track, trackFileFormat, new TrackWriter.OnWriteListener() {
|
||||
@Override
|
||||
public void onWrite(int number, int max) {
|
||||
/*
|
||||
* If only saving one track, update the progress dialog once every
|
||||
* 500 points
|
||||
*/
|
||||
if (trackId != -1L && number % 500 == 0) {
|
||||
publishProgress(number, max);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
File file = null;
|
||||
try {
|
||||
file = new File(directory, fileName);
|
||||
OutputStream outputStream = new FileOutputStream(file);
|
||||
trackWriter.writeTrack(outputStream);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.d(TAG, "File not found " + fileName, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trackWriter.wasSuccess()) {
|
||||
savedPath = file.getAbsolutePath();
|
||||
} else {
|
||||
if (!file.delete()) {
|
||||
Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
|
||||
}
|
||||
});
|
||||
trackWriter.writeTrack();
|
||||
messageId = trackWriter.getErrorMessage();
|
||||
savedPath = trackWriter.getAbsolutePath();
|
||||
}
|
||||
return trackWriter.wasSuccess();
|
||||
}
|
||||
|
||||
@@ -169,27 +221,21 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
try {
|
||||
cursor = myTracksProviderUtils.getTrackCursor(null, null, TracksColumns._ID);
|
||||
if (cursor == null) {
|
||||
messageId = R.string.external_storage_save_error_no_track;
|
||||
return false;
|
||||
}
|
||||
int count = cursor.getCount();
|
||||
if (count == 0) {
|
||||
messageId = R.string.external_storage_save_error_no_track;
|
||||
return false;
|
||||
}
|
||||
totalCount = cursor.getCount();
|
||||
int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
||||
for (int i = 0; i < count; i++) {
|
||||
for (int i = 0; i < totalCount; i++) {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
cursor.moveToPosition(i);
|
||||
long id = cursor.getLong(idIndex);
|
||||
if (!saveOneTrack(id, false)) {
|
||||
return false;
|
||||
}
|
||||
publishProgress(i + 1, count);
|
||||
if (saveOneTrack(id)) {
|
||||
successCount++;
|
||||
}
|
||||
publishProgress(i + 1, totalCount);
|
||||
}
|
||||
messageId = R.string.external_storage_save_success;
|
||||
return true;
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
@@ -197,26 +243,4 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values) {
|
||||
if (saveActivity != null) {
|
||||
saveActivity.setProgressDialogValue(values[0], values[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
success = result;
|
||||
completed = true;
|
||||
if (saveActivity != null) {
|
||||
saveActivity.onAsyncTaskCompleted(success, messageId, savedPath);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
if (trackWriter != null) {
|
||||
trackWriter.stopWriteTrack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,65 +13,267 @@
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import java.io.File;
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Implementations of this class export tracks to the SD card. This class is
|
||||
* intended to be format-neutral - it handles creating the output file and
|
||||
* reading the track to be exported, but requires an instance of
|
||||
* {@link TrackFormatWriter} to actually format the data.
|
||||
*
|
||||
* Track Writer for writing a track to an {@link OutputStream}.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public interface TrackWriter {
|
||||
public class TrackWriter {
|
||||
|
||||
/** This listener is used to signal track writes. */
|
||||
/**
|
||||
* Listener for when a track location is written.
|
||||
*/
|
||||
public interface OnWriteListener {
|
||||
|
||||
/**
|
||||
* This method is invoked whenever a location within a track is written.
|
||||
* When a track location is written.
|
||||
*
|
||||
* @param number the location number
|
||||
* @param max the maximum number of locations, for calculation of
|
||||
* completion percentage
|
||||
* @param max the maximum number of locations, for calculation of completion
|
||||
* percentage
|
||||
*/
|
||||
public void onWrite(int number, int max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a listener to be invoked for each location writer.
|
||||
*/
|
||||
void setOnWriteListener(OnWriteListener onWriteListener);
|
||||
private final static String TAG = TrackWriter.class.getSimpleName();
|
||||
|
||||
private final MyTracksProviderUtils myTracksProviderUtils;
|
||||
private final Track track;
|
||||
private final TrackFormatWriter trackFormatWriter;
|
||||
private final OnWriteListener onWriteListener;
|
||||
|
||||
private Thread writeThread;
|
||||
private boolean success = false;
|
||||
|
||||
/**
|
||||
* Sets a custom directory where the file will be written.
|
||||
* Constructor.
|
||||
*
|
||||
* @param context the context
|
||||
* @param myTracksProviderUtils the my tracks provider utils
|
||||
* @param track the track
|
||||
* @param trackFileFormat the track file format
|
||||
* @param onWriteListener the on write listener
|
||||
*/
|
||||
void setDirectory(File directory);
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the file which was created.
|
||||
*/
|
||||
String getAbsolutePath();
|
||||
|
||||
/**
|
||||
* Writes the given track id to the SD card.
|
||||
* This is blocking.
|
||||
*/
|
||||
void writeTrack();
|
||||
|
||||
/**
|
||||
* Stop any in-progress writes
|
||||
*/
|
||||
void stopWriteTrack();
|
||||
public TrackWriter(Context context, MyTracksProviderUtils myTracksProviderUtils, Track track,
|
||||
TrackFileFormat trackFileFormat, OnWriteListener onWriteListener) {
|
||||
this(myTracksProviderUtils, track, trackFileFormat.newFormatWriter(context), onWriteListener);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public TrackWriter(MyTracksProviderUtils myTracksProviderUtils, Track track,
|
||||
TrackFormatWriter trackFormatWriter, OnWriteListener onWriteListener) {
|
||||
this.myTracksProviderUtils = myTracksProviderUtils;
|
||||
this.track = track;
|
||||
this.trackFormatWriter = trackFormatWriter;
|
||||
this.onWriteListener = onWriteListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the write completed successfully.
|
||||
*/
|
||||
boolean wasSuccess();
|
||||
public boolean wasSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error message (if any) generated by a writer failure.
|
||||
* Writes the given track to the output stream.
|
||||
*
|
||||
* @param outputStream the output stream.
|
||||
*/
|
||||
int getErrorMessage();
|
||||
public void writeTrack(final OutputStream outputStream) {
|
||||
writeThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
trackFormatWriter.prepare(track, outputStream);
|
||||
trackFormatWriter.writeHeader();
|
||||
writeWaypoints();
|
||||
writeLocations();
|
||||
trackFormatWriter.writeFooter();
|
||||
trackFormatWriter.close();
|
||||
success = true;
|
||||
} catch (InterruptedException e) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
writeThread.start();
|
||||
try {
|
||||
writeThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Interrupted while waiting for write to complete", e);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops any in-progress writes.
|
||||
*/
|
||||
public void stopWriteTrack() {
|
||||
if (writeThread != null && writeThread.isAlive()) {
|
||||
Log.i(TAG, "Attempting to stop track write");
|
||||
writeThread.interrupt();
|
||||
|
||||
try {
|
||||
writeThread.join();
|
||||
Log.i(TAG, "Track write stopped");
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Interrupted while waiting for writer to stop", e);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the waypoints.
|
||||
*/
|
||||
private void writeWaypoints() {
|
||||
/*
|
||||
* TODO: Stream through the waypoints in chunks. I am leaving the number of
|
||||
* waypoints very high which should not be a problem because we don't try to
|
||||
* load them into objects all at the same time.
|
||||
*/
|
||||
Cursor cursor = myTracksProviderUtils.getWaypointCursor(
|
||||
track.getId(), -1L, Constants.MAX_LOADED_WAYPOINTS_POINTS);
|
||||
boolean hasWaypoints = false;
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
/*
|
||||
* Yes, this will skip the first waypoint and that is intentional as
|
||||
* the first waypoint holds the stats for the track.
|
||||
*/
|
||||
while (cursor.moveToNext()) {
|
||||
if (!hasWaypoints) {
|
||||
trackFormatWriter.writeBeginWaypoints();
|
||||
hasWaypoints = true;
|
||||
}
|
||||
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
|
||||
trackFormatWriter.writeWaypoint(waypoint);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
if (hasWaypoints) {
|
||||
trackFormatWriter.writeEndWaypoints();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the locations.
|
||||
*/
|
||||
private void writeLocations() throws InterruptedException {
|
||||
boolean wroteTrack = false;
|
||||
boolean wroteSegment = false;
|
||||
boolean isLastLocationValid = false;
|
||||
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
|
||||
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(
|
||||
track.getId(), -1L, false, locationFactory);
|
||||
|
||||
try {
|
||||
int locationNumber = 0;
|
||||
while (iterator.hasNext()) {
|
||||
Location location = iterator.next();
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
locationNumber++;
|
||||
|
||||
boolean isLocationValid = LocationUtils.isValidLocation(location);
|
||||
boolean isSegmentValid = isLocationValid && isLastLocationValid;
|
||||
if (!wroteTrack && isSegmentValid) {
|
||||
// Found the first two consecutive locations that are valid
|
||||
trackFormatWriter.writeBeginTrack(locationFactory.lastLocation);
|
||||
wroteTrack = true;
|
||||
}
|
||||
|
||||
if (isSegmentValid) {
|
||||
if (!wroteSegment) {
|
||||
// Start a segment
|
||||
trackFormatWriter.writeOpenSegment();
|
||||
wroteSegment = true;
|
||||
|
||||
// Write the previous location, which we had previously skipped
|
||||
trackFormatWriter.writeLocation(locationFactory.lastLocation);
|
||||
}
|
||||
|
||||
// Write the current location
|
||||
trackFormatWriter.writeLocation(location);
|
||||
if (onWriteListener != null) {
|
||||
onWriteListener.onWrite(locationNumber, track.getNumberOfPoints());
|
||||
}
|
||||
} else {
|
||||
if (wroteSegment) {
|
||||
trackFormatWriter.writeCloseSegment();
|
||||
wroteSegment = false;
|
||||
}
|
||||
}
|
||||
locationFactory.swapLocations();
|
||||
isLastLocationValid = isLocationValid;
|
||||
}
|
||||
|
||||
if (wroteSegment) {
|
||||
trackFormatWriter.writeCloseSegment();
|
||||
wroteSegment = false;
|
||||
}
|
||||
if (wroteTrack) {
|
||||
trackFormatWriter.writeEndTrack(locationFactory.lastLocation);
|
||||
} else {
|
||||
// Write an empty track
|
||||
trackFormatWriter.writeBeginTrack(null);
|
||||
trackFormatWriter.writeEndTrack(null);
|
||||
}
|
||||
} finally {
|
||||
iterator.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track writer location factory. Keeping the last two locations.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
private class TrackWriterLocationFactory implements MyTracksProviderUtils.LocationFactory {
|
||||
Location currentLocation;
|
||||
Location lastLocation;
|
||||
|
||||
@Override
|
||||
public Location createLocation() {
|
||||
if (currentLocation == null) {
|
||||
currentLocation = new MyTracksLocation("");
|
||||
}
|
||||
return currentLocation;
|
||||
}
|
||||
|
||||
public void swapLocations() {
|
||||
Location tempLocation = lastLocation;
|
||||
lastLocation = currentLocation;
|
||||
currentLocation = tempLocation;
|
||||
if (currentLocation != null) {
|
||||
currentLocation.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 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.io.file;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
/**
|
||||
* A factory to produce track writers for any format.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackWriterFactory {
|
||||
|
||||
private TrackWriterFactory() {}
|
||||
|
||||
/**
|
||||
* Creates a new track writer to write the track with the given ID.
|
||||
*
|
||||
* @param context the context in which the track will be read
|
||||
* @param providerUtils the data provider utils to read the track with
|
||||
* @param trackId the ID of the track to be written
|
||||
* @param format the output format to write in
|
||||
* @return the new track writer
|
||||
*/
|
||||
public static TrackWriter newWriter(
|
||||
Context context, MyTracksProviderUtils providerUtils, long trackId, TrackFileFormat format) {
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.d(TAG, "No track for " + trackId);
|
||||
return null;
|
||||
}
|
||||
|
||||
TrackFormatWriter writer = format.newFormatWriter(context);
|
||||
return new TrackWriterImpl(context, providerUtils, track, writer);
|
||||
}
|
||||
}
|
||||
@@ -1,373 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008 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.io.file;
|
||||
|
||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.util.FileUtils;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* This class exports tracks to the SD card. It is intended to be format-
|
||||
* neutral -- it handles creating the output file and reading the track to be
|
||||
* exported, but requires an instance of {@link TrackFormatWriter} to actually
|
||||
* format the data.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
class TrackWriterImpl implements TrackWriter {
|
||||
private final Context context;
|
||||
private final MyTracksProviderUtils providerUtils;
|
||||
private final Track track;
|
||||
private final TrackFormatWriter writer;
|
||||
private boolean success = false;
|
||||
private int errorMessage = R.string.external_storage_save_error;
|
||||
private File directory = null;
|
||||
private File file = null;
|
||||
private OnWriteListener onWriteListener;
|
||||
private Thread writeThread;
|
||||
|
||||
TrackWriterImpl(Context context, MyTracksProviderUtils providerUtils,
|
||||
Track track, TrackFormatWriter writer) {
|
||||
this.context = context;
|
||||
this.providerUtils = providerUtils;
|
||||
this.track = track;
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnWriteListener(OnWriteListener onWriteListener) {
|
||||
this.onWriteListener = onWriteListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDirectory(File directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAbsolutePath() {
|
||||
return file == null ? null : file.getAbsolutePath();
|
||||
}
|
||||
|
||||
private void writeTrackAsync() {
|
||||
writeThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
doWriteTrack();
|
||||
}
|
||||
};
|
||||
writeThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTrack() {
|
||||
writeTrackAsync();
|
||||
try {
|
||||
writeThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(Constants.TAG, "Interrupted waiting for write to complete", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void doWriteTrack() {
|
||||
// Open the input and output
|
||||
success = false;
|
||||
errorMessage = R.string.external_storage_save_error;
|
||||
if (track != null) {
|
||||
if (openFile()) {
|
||||
try {
|
||||
writeDocument();
|
||||
} catch (InterruptedException e) {
|
||||
Log.i(Constants.TAG, "The track write was interrupted");
|
||||
if (file != null && !file.delete()) {
|
||||
Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
|
||||
}
|
||||
success = false;
|
||||
errorMessage = R.string.external_storage_canceled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stopWriteTrack() {
|
||||
if (writeThread != null && writeThread.isAlive()) {
|
||||
Log.i(Constants.TAG, "Attempting to stop track write");
|
||||
writeThread.interrupt();
|
||||
|
||||
try {
|
||||
writeThread.join();
|
||||
Log.i(Constants.TAG, "Track write stopped");
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(Constants.TAG, "Failed to wait for writer to stop", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper methods:
|
||||
* ===============
|
||||
*/
|
||||
|
||||
/**
|
||||
* Runs the given runnable in the UI thread.
|
||||
*/
|
||||
protected void runOnUiThread(Runnable runnable) {
|
||||
if (context instanceof Activity) {
|
||||
((Activity) context).runOnUiThread(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the file and prepares the format writer for it.
|
||||
*
|
||||
* @return true on success, false otherwise (and errorMessage is set)
|
||||
*/
|
||||
protected boolean openFile() {
|
||||
if (!canWriteFile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the file doesn't exist yet (possibly by changing the filename)
|
||||
String fileName = FileUtils.buildUniqueFileName(
|
||||
directory, track.getName(), writer.getExtension());
|
||||
if (fileName == null) {
|
||||
Log.e(Constants.TAG,
|
||||
"Unable to get a unique filename for " + track.getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.i(Constants.TAG, "Writing track to: " + fileName);
|
||||
try {
|
||||
writer.prepare(track, newOutputStream(fileName));
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Failed to open output file.", e);
|
||||
errorMessage = R.string.external_storage_save_error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and returns whether we're ready to create the output file.
|
||||
*/
|
||||
protected boolean canWriteFile() {
|
||||
if (!FileUtils.isExternalStorageWriteable()) {
|
||||
errorMessage = R.string.external_storage_not_writeable;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (directory == null) {
|
||||
String dirName =
|
||||
FileUtils.buildExternalDirectoryPath(writer.getExtension());
|
||||
directory = newFile(dirName);
|
||||
}
|
||||
if (!FileUtils.ensureDirectoryExists(directory)) {
|
||||
Log.i(Constants.TAG, "Could not create export directory.");
|
||||
errorMessage = R.string.external_storage_save_error_create_dir;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new output stream to write to the given filename.
|
||||
*
|
||||
* @throws FileNotFoundException if the file could't be created
|
||||
*/
|
||||
protected OutputStream newOutputStream(String fileName)
|
||||
throws FileNotFoundException {
|
||||
file = new File(directory, fileName);
|
||||
return new FileOutputStream(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new file object for the given path.
|
||||
*/
|
||||
protected File newFile(String path) {
|
||||
return new File(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the waypoints for the given track.
|
||||
*
|
||||
* @param trackId the ID of the track to write waypoints for
|
||||
*/
|
||||
private void writeWaypoints(long trackId) {
|
||||
// TODO: Stream through he waypoints in chunks.
|
||||
// I am leaving the number of waypoints very high which should not be a
|
||||
// problem because we don't try to load them into objects all at the
|
||||
// same time.
|
||||
Cursor cursor = null;
|
||||
cursor = providerUtils.getWaypointCursor(trackId, -1L,
|
||||
Constants.MAX_LOADED_WAYPOINTS_POINTS);
|
||||
boolean hasWaypoints = false;
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
// Yes, this will skip the 1st way point and that is intentional
|
||||
// as the 1st points holds the stats for the current/last segment.
|
||||
while (cursor.moveToNext()) {
|
||||
if (!hasWaypoints) {
|
||||
writer.writeBeginWaypoints();
|
||||
hasWaypoints = true;
|
||||
}
|
||||
Waypoint wpt = providerUtils.createWaypoint(cursor);
|
||||
writer.writeWaypoint(wpt);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
if (hasWaypoints) {
|
||||
writer.writeEndWaypoints();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual work of writing the track to the now open file.
|
||||
*/
|
||||
void writeDocument() throws InterruptedException {
|
||||
Log.d(Constants.TAG, "Started writing track.");
|
||||
writer.writeHeader();
|
||||
writeWaypoints(track.getId());
|
||||
writeLocations();
|
||||
writer.writeFooter();
|
||||
writer.close();
|
||||
success = true;
|
||||
Log.d(Constants.TAG, "Done writing track.");
|
||||
errorMessage = R.string.external_storage_save_success;
|
||||
}
|
||||
|
||||
private void writeLocations() throws InterruptedException {
|
||||
boolean wroteFirst = false;
|
||||
boolean segmentOpen = false;
|
||||
boolean isLastValid = false;
|
||||
|
||||
class TrackWriterLocationFactory implements MyTracksProviderUtils.LocationFactory {
|
||||
Location currentLocation;
|
||||
Location lastLocation;
|
||||
|
||||
@Override
|
||||
public Location createLocation() {
|
||||
if (currentLocation == null) {
|
||||
currentLocation = new MyTracksLocation("");
|
||||
}
|
||||
return currentLocation;
|
||||
}
|
||||
|
||||
public void swapLocations() {
|
||||
Location tmpLoc = lastLocation;
|
||||
lastLocation = currentLocation;
|
||||
currentLocation = tmpLoc;
|
||||
if (currentLocation != null) {
|
||||
currentLocation.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
|
||||
LocationIterator it = providerUtils.getTrackPointLocationIterator(track.getId(), -1L, false,
|
||||
locationFactory);
|
||||
try {
|
||||
int pointNumber = 0;
|
||||
while (it.hasNext()) {
|
||||
Location location = it.next();
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
|
||||
pointNumber++;
|
||||
|
||||
boolean isValid = LocationUtils.isValidLocation(location);
|
||||
boolean validSegment = isValid && isLastValid;
|
||||
if (!wroteFirst && validSegment) {
|
||||
// Found the first two consecutive points which are valid
|
||||
writer.writeBeginTrack(locationFactory.lastLocation);
|
||||
wroteFirst = true;
|
||||
}
|
||||
|
||||
if (validSegment) {
|
||||
if (!segmentOpen) {
|
||||
// Start a segment for this point
|
||||
writer.writeOpenSegment();
|
||||
segmentOpen = true;
|
||||
|
||||
// Write the previous point, which we had previously skipped
|
||||
writer.writeLocation(locationFactory.lastLocation);
|
||||
}
|
||||
|
||||
// Write the current point
|
||||
writer.writeLocation(location);
|
||||
if (onWriteListener != null) {
|
||||
onWriteListener.onWrite(pointNumber, track.getNumberOfPoints());
|
||||
}
|
||||
} else {
|
||||
if (segmentOpen) {
|
||||
writer.writeCloseSegment();
|
||||
segmentOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
locationFactory.swapLocations();
|
||||
isLastValid = isValid;
|
||||
}
|
||||
if (segmentOpen) {
|
||||
writer.writeCloseSegment();
|
||||
segmentOpen = false;
|
||||
}
|
||||
if (wroteFirst) {
|
||||
writer.writeEndTrack(locationFactory.lastLocation);
|
||||
} else {
|
||||
// Writes an empty track
|
||||
writer.writeBeginTrack(null);
|
||||
writer.writeEndTrack(null);
|
||||
}
|
||||
} finally {
|
||||
it.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +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.io.file;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* A simple, fake {@link TrackWriter} subclass with all methods mocked out.
|
||||
* Tests are expected to override {@link #writeTrack}.
|
||||
*
|
||||
* @author Matthew Simmons
|
||||
*
|
||||
*/
|
||||
public class MockTrackWriter implements TrackWriter {
|
||||
public OnWriteListener onWriteListener;
|
||||
|
||||
@Override
|
||||
public void setOnWriteListener(OnWriteListener onWriteListener) {
|
||||
this.onWriteListener = onWriteListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDirectory(File directory) {
|
||||
throw new UnsupportedOperationException("not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAbsolutePath() {
|
||||
throw new UnsupportedOperationException("not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTrack() {
|
||||
throw new UnsupportedOperationException("not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopWriteTrack() {
|
||||
throw new UnsupportedOperationException("not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasSuccess() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getErrorMessage() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
package com.google.android.apps.mytracks.io.file;
|
||||
|
||||
import static org.easymock.EasyMock.expect;
|
||||
|
||||
import com.google.android.apps.mytracks.content.MyTracksProvider;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.io.file.TrackWriter.OnWriteListener;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext;
|
||||
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
|
||||
|
||||
@@ -19,8 +18,6 @@ import android.test.RenamingDelegatingContext;
|
||||
import android.test.mock.MockContentResolver;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.easymock.EasyMock;
|
||||
@@ -28,112 +25,24 @@ import org.easymock.IArgumentMatcher;
|
||||
import org.easymock.IMocksControl;
|
||||
|
||||
/**
|
||||
* Tests for the track writer.
|
||||
*
|
||||
* Tests for {@link TrackWriter}.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackWriterTest extends AndroidTestCase {
|
||||
/**
|
||||
* {@link TrackWriterImpl} subclass which mocks out methods called from
|
||||
* {@link TrackWriterImpl#openFile}.
|
||||
*/
|
||||
private static final class OpenFileTrackWriter extends TrackWriterImpl {
|
||||
private final ByteArrayOutputStream stream;
|
||||
private final boolean canWrite;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param stream the stream to return from
|
||||
* {@link TrackWriterImpl#newOutputStream}, or null to throw a
|
||||
* {@link FileNotFoundException}
|
||||
* @param canWrite the value that {@link TrackWriterImpl#canWriteFile} will
|
||||
* return
|
||||
*/
|
||||
private OpenFileTrackWriter(Context context,
|
||||
MyTracksProviderUtils providerUtils, Track track,
|
||||
TrackFormatWriter writer, ByteArrayOutputStream stream,
|
||||
boolean canWrite) {
|
||||
super(context, providerUtils, track, writer);
|
||||
|
||||
this.stream = stream;
|
||||
this.canWrite = canWrite;
|
||||
|
||||
// The directory is set in the canWriteFile. However, this class
|
||||
// overwrites canWriteFile, thus needs to set it.
|
||||
setDirectory(new File("/"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canWriteFile() {
|
||||
return canWrite;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream newOutputStream(String fileName)
|
||||
throws FileNotFoundException {
|
||||
assertEquals(FULL_TRACK_NAME, fileName);
|
||||
|
||||
if (stream == null) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link TrackWriterImpl} subclass which mocks out methods called from
|
||||
* {@link TrackWriterImpl#writeTrack}.
|
||||
*/
|
||||
private final class WriteTracksTrackWriter extends TrackWriterImpl {
|
||||
private final boolean openResult;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param openResult the return value for {@link TrackWriterImpl#openFile}
|
||||
*/
|
||||
private WriteTracksTrackWriter(Context context,
|
||||
MyTracksProviderUtils providerUtils, Track track,
|
||||
TrackFormatWriter writer, boolean openResult) {
|
||||
super(context, providerUtils, track, writer);
|
||||
this.openResult = openResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean openFile() {
|
||||
openFileCalls++;
|
||||
return openResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeDocument() {
|
||||
writeDocumentCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runOnUiThread(Runnable runnable) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
private static final long TRACK_ID = 1234567L;
|
||||
private static final String EXTENSION = "ext";
|
||||
private static final String TRACK_NAME = "Swimming across the pacific";
|
||||
private static final String FULL_TRACK_NAME =
|
||||
"Swimming across the pacific.ext";
|
||||
|
||||
private Track track;
|
||||
private TrackFormatWriter formatWriter;
|
||||
private TrackWriterImpl writer;
|
||||
private IMocksControl mocksControl;
|
||||
private MyTracksProviderUtils providerUtils;
|
||||
private MyTracksProviderUtils myTracksProviderUtils;
|
||||
private Factory oldProviderUtilsFactory;
|
||||
|
||||
// State used in specific tests
|
||||
private int writeDocumentCalls;
|
||||
private int openFileCalls;
|
||||
private IMocksControl mocksControl;
|
||||
|
||||
private TrackFormatWriter trackFormatWriter;
|
||||
private Track track;
|
||||
private OutputStream outputStream;
|
||||
private TrackWriter trackWriter;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
@@ -143,20 +52,31 @@ public class TrackWriterTest extends AndroidTestCase {
|
||||
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
|
||||
getContext(), getContext(), "test.");
|
||||
Context context = new MockContext(mockContentResolver, targetContext);
|
||||
MyTracksProvider provider = new MyTracksProvider();
|
||||
provider.attachInfo(context, null);
|
||||
mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, provider);
|
||||
MyTracksProvider myTracksProvider = new MyTracksProvider();
|
||||
myTracksProvider.attachInfo(context, null);
|
||||
mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, myTracksProvider);
|
||||
setContext(context);
|
||||
providerUtils = MyTracksProviderUtils.Factory.get(context);
|
||||
oldProviderUtilsFactory = TestingProviderUtilsFactory.installWithInstance(providerUtils);
|
||||
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
|
||||
oldProviderUtilsFactory = TestingProviderUtilsFactory.installWithInstance(
|
||||
myTracksProviderUtils);
|
||||
|
||||
mocksControl = EasyMock.createStrictControl();
|
||||
formatWriter = mocksControl.createMock(TrackFormatWriter.class);
|
||||
expect(formatWriter.getExtension()).andStubReturn(EXTENSION);
|
||||
trackFormatWriter = mocksControl.createMock(TrackFormatWriter.class);
|
||||
|
||||
track = new Track();
|
||||
track.setName(TRACK_NAME);
|
||||
track.setId(TRACK_ID);
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
OnWriteListener onWriteListener = new OnWriteListener() {
|
||||
|
||||
@Override
|
||||
public void onWrite(int number, int max) {
|
||||
// Safe to ignore
|
||||
|
||||
}
|
||||
};
|
||||
trackWriter = new TrackWriter(myTracksProviderUtils, track, trackFormatWriter, onWriteListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,226 +85,196 @@ public class TrackWriterTest extends AndroidTestCase {
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void testWriteTrack() {
|
||||
writer = new WriteTracksTrackWriter(getContext(), providerUtils, track,
|
||||
formatWriter, true);
|
||||
|
||||
mocksControl.replay();
|
||||
writer.writeTrack();
|
||||
|
||||
assertEquals(1, writeDocumentCalls);
|
||||
assertEquals(1, openFileCalls);
|
||||
mocksControl.verify();
|
||||
}
|
||||
|
||||
public void testWriteTrack_openFails() {
|
||||
writer = new WriteTracksTrackWriter(getContext(), providerUtils, track,
|
||||
formatWriter, false);
|
||||
|
||||
mocksControl.replay();
|
||||
writer.writeTrack();
|
||||
|
||||
assertEquals(0, writeDocumentCalls);
|
||||
assertEquals(1, openFileCalls);
|
||||
mocksControl.verify();
|
||||
}
|
||||
|
||||
public void testOpenFile() {
|
||||
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
writer = new OpenFileTrackWriter(
|
||||
getContext(), providerUtils, track, formatWriter, stream, true);
|
||||
|
||||
formatWriter.prepare(track, stream);
|
||||
|
||||
mocksControl.replay();
|
||||
assertTrue(writer.openFile());
|
||||
mocksControl.verify();
|
||||
}
|
||||
|
||||
public void testOpenFile_cantWrite() {
|
||||
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
writer = new OpenFileTrackWriter(
|
||||
getContext(), providerUtils, track, formatWriter, stream, false);
|
||||
|
||||
mocksControl.replay();
|
||||
assertFalse(writer.openFile());
|
||||
mocksControl.verify();
|
||||
}
|
||||
|
||||
public void testOpenFile_streamError() {
|
||||
writer = new OpenFileTrackWriter(
|
||||
getContext(), providerUtils, track, formatWriter, null, true);
|
||||
|
||||
mocksControl.replay();
|
||||
assertFalse(writer.openFile());
|
||||
mocksControl.verify();
|
||||
}
|
||||
|
||||
public void testWriteDocument_emptyTrack() throws Exception {
|
||||
writer = new TrackWriterImpl(getContext(), providerUtils, track, formatWriter);
|
||||
/**
|
||||
* Tests write track with an empty track.
|
||||
*/
|
||||
public void testWriteTrack_emptyTrack() throws Exception {
|
||||
|
||||
// Set expected mock behavior
|
||||
formatWriter.writeHeader();
|
||||
formatWriter.writeBeginTrack(null);
|
||||
formatWriter.writeEndTrack(null);
|
||||
formatWriter.writeFooter();
|
||||
formatWriter.close();
|
||||
trackFormatWriter.prepare(track, outputStream);
|
||||
trackFormatWriter.writeHeader();
|
||||
trackFormatWriter.writeBeginTrack(null);
|
||||
trackFormatWriter.writeEndTrack(null);
|
||||
trackFormatWriter.writeFooter();
|
||||
trackFormatWriter.close();
|
||||
|
||||
mocksControl.replay();
|
||||
writer.writeDocument();
|
||||
trackWriter.writeTrack(outputStream);
|
||||
|
||||
assertTrue(writer.wasSuccess());
|
||||
assertTrue(trackWriter.wasSuccess());
|
||||
mocksControl.verify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests when a track only contains invalid locations. Make sure an empty
|
||||
* track is written.
|
||||
* Tests write track with invalid locations. Make sure an empty track is
|
||||
* written.
|
||||
*/
|
||||
public void testWriteDocument_oneInvalidLocation() throws Exception {
|
||||
writer = new TrackWriterImpl(getContext(), providerUtils, track, formatWriter);
|
||||
public void testWriteTrack_oneInvalidLocation() throws Exception {
|
||||
|
||||
Location[] locs = { new Location("fake0") };
|
||||
fillLocations(locs);
|
||||
// Add two locations
|
||||
Location[] locations = { new Location("fake0"), new Location("fake1") };
|
||||
fillLocations(locations);
|
||||
|
||||
// Make location invalid
|
||||
locs[0].setLatitude(100);
|
||||
// Make locations invalid
|
||||
locations[0].setLatitude(100.0);
|
||||
locations[1].setLatitude(100.0);
|
||||
|
||||
assertEquals(locs.length, providerUtils.bulkInsertTrackPoint(locs, locs.length, TRACK_ID));
|
||||
assertEquals(locations.length,
|
||||
myTracksProviderUtils.bulkInsertTrackPoint(locations, locations.length, TRACK_ID));
|
||||
|
||||
formatWriter.writeHeader();
|
||||
formatWriter.writeBeginTrack(null);
|
||||
formatWriter.writeEndTrack(null);
|
||||
formatWriter.writeFooter();
|
||||
formatWriter.close();
|
||||
// Set expected mock behavior
|
||||
trackFormatWriter.prepare(track, outputStream);
|
||||
trackFormatWriter.writeHeader();
|
||||
trackFormatWriter.writeBeginTrack(null);
|
||||
trackFormatWriter.writeEndTrack(null);
|
||||
trackFormatWriter.writeFooter();
|
||||
trackFormatWriter.close();
|
||||
|
||||
mocksControl.replay();
|
||||
writer.writeDocument();
|
||||
trackWriter.writeTrack(outputStream);
|
||||
|
||||
assertTrue(writer.wasSuccess());
|
||||
assertTrue(trackWriter.wasSuccess());
|
||||
mocksControl.verify();
|
||||
}
|
||||
|
||||
public void testWriteDocument() throws Exception {
|
||||
writer = new TrackWriterImpl(getContext(), providerUtils, track, formatWriter);
|
||||
/**
|
||||
* Tests write track.
|
||||
*/
|
||||
public void testWriteTrack() throws Exception {
|
||||
|
||||
final Location[] locs = {
|
||||
new Location("fake0"),
|
||||
new Location("fake1"),
|
||||
new Location("fake2"),
|
||||
new Location("fake3"),
|
||||
new Location("fake4"),
|
||||
new Location("fake5")
|
||||
};
|
||||
Waypoint[] wps = { new Waypoint(), new Waypoint(), new Waypoint() };
|
||||
|
||||
// Fill locations with valid values
|
||||
fillLocations(locs);
|
||||
// Add six locations
|
||||
Location[] locations = { new Location("fake0"), new Location("fake1"), new Location("fake2"),
|
||||
new Location("fake3"), new Location("fake4"), new Location("fake5") };
|
||||
fillLocations(locations);
|
||||
|
||||
// Make location 3 invalid
|
||||
locs[2].setLatitude(100);
|
||||
locations[2].setLatitude(100.0);
|
||||
|
||||
assertEquals(locs.length, providerUtils.bulkInsertTrackPoint(locs, locs.length, TRACK_ID));
|
||||
for (int i = 0; i < wps.length; ++i) {
|
||||
Waypoint wpt = wps[i];
|
||||
wpt.setTrackId(TRACK_ID);
|
||||
assertNotNull(providerUtils.insertWaypoint(wpt));
|
||||
wpt.setId(i + 1);
|
||||
assertEquals(locations.length,
|
||||
myTracksProviderUtils.bulkInsertTrackPoint(locations, locations.length, TRACK_ID));
|
||||
|
||||
Waypoint[] waypoints = { new Waypoint(), new Waypoint(), new Waypoint() };
|
||||
|
||||
for (int i = 0; i < waypoints.length; i++) {
|
||||
Waypoint waypoint = waypoints[i];
|
||||
waypoint.setTrackId(TRACK_ID);
|
||||
assertNotNull(myTracksProviderUtils.insertWaypoint(waypoint));
|
||||
waypoint.setId(i + 1);
|
||||
}
|
||||
|
||||
formatWriter.writeHeader();
|
||||
trackFormatWriter.prepare(track, outputStream);
|
||||
trackFormatWriter.writeHeader();
|
||||
|
||||
// Expect reading/writing of the waypoints (except the first)
|
||||
formatWriter.writeBeginWaypoints();
|
||||
formatWriter.writeWaypoint(wptEq(wps[1]));
|
||||
formatWriter.writeWaypoint(wptEq(wps[2]));
|
||||
formatWriter.writeEndWaypoints();
|
||||
trackFormatWriter.writeBeginWaypoints();
|
||||
trackFormatWriter.writeWaypoint(waypointEq(waypoints[1]));
|
||||
trackFormatWriter.writeWaypoint(waypointEq(waypoints[2]));
|
||||
trackFormatWriter.writeEndWaypoints();
|
||||
|
||||
// Begin the track
|
||||
formatWriter.writeBeginTrack(locEq(locs[0]));
|
||||
trackFormatWriter.writeBeginTrack(locationEq(locations[0]));
|
||||
|
||||
// Write locations 1-2
|
||||
formatWriter.writeOpenSegment();
|
||||
formatWriter.writeLocation(locEq(locs[0]));
|
||||
formatWriter.writeLocation(locEq(locs[1]));
|
||||
formatWriter.writeCloseSegment();
|
||||
trackFormatWriter.writeOpenSegment();
|
||||
trackFormatWriter.writeLocation(locationEq(locations[0]));
|
||||
trackFormatWriter.writeLocation(locationEq(locations[1]));
|
||||
trackFormatWriter.writeCloseSegment();
|
||||
|
||||
// Location 3 is not written - it's invalid
|
||||
|
||||
// Write locations 4-6
|
||||
formatWriter.writeOpenSegment();
|
||||
formatWriter.writeLocation(locEq(locs[3]));
|
||||
formatWriter.writeLocation(locEq(locs[4]));
|
||||
formatWriter.writeLocation(locEq(locs[5]));
|
||||
formatWriter.writeCloseSegment();
|
||||
trackFormatWriter.writeOpenSegment();
|
||||
trackFormatWriter.writeLocation(locationEq(locations[3]));
|
||||
trackFormatWriter.writeLocation(locationEq(locations[4]));
|
||||
trackFormatWriter.writeLocation(locationEq(locations[5]));
|
||||
trackFormatWriter.writeCloseSegment();
|
||||
|
||||
// End the track
|
||||
formatWriter.writeEndTrack(locEq(locs[5]));
|
||||
trackFormatWriter.writeEndTrack(locationEq(locations[5]));
|
||||
|
||||
formatWriter.writeFooter();
|
||||
formatWriter.close();
|
||||
trackFormatWriter.writeFooter();
|
||||
trackFormatWriter.close();
|
||||
|
||||
mocksControl.replay();
|
||||
writer.writeDocument();
|
||||
trackWriter.writeTrack(outputStream);
|
||||
|
||||
assertTrue(writer.wasSuccess());
|
||||
assertTrue(trackWriter.wasSuccess());
|
||||
mocksControl.verify();
|
||||
}
|
||||
|
||||
private static Waypoint wptEq(final Waypoint wpt) {
|
||||
/**
|
||||
* Waypoint equals.
|
||||
*
|
||||
* @param waypoint the waypoint
|
||||
*/
|
||||
private Waypoint waypointEq(final Waypoint waypoint) {
|
||||
EasyMock.reportMatcher(new IArgumentMatcher() {
|
||||
@Override
|
||||
public boolean matches(Object wptObj2) {
|
||||
if (wptObj2 == null || wpt == null) return wpt == wptObj2;
|
||||
Waypoint wpt2 = (Waypoint) wptObj2;
|
||||
@Override
|
||||
public boolean matches(Object object) {
|
||||
if (object == null || waypoint == null) {
|
||||
return waypoint == object;
|
||||
}
|
||||
Waypoint waypoint2 = (Waypoint) object;
|
||||
|
||||
return wpt.getId() == wpt2.getId();
|
||||
return waypoint.getId() == waypoint2.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public void appendTo(StringBuffer buffer) {
|
||||
buffer.append("wptEq(");
|
||||
buffer.append(wpt);
|
||||
buffer.append("waypointEq(");
|
||||
buffer.append(waypoint);
|
||||
buffer.append(")");
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Location locEq(final Location loc) {
|
||||
/**
|
||||
* Location equals.
|
||||
*
|
||||
* @param location the location
|
||||
*/
|
||||
private Location locationEq(final Location location) {
|
||||
EasyMock.reportMatcher(new IArgumentMatcher() {
|
||||
@Override
|
||||
public boolean matches(Object locObj2) {
|
||||
if (locObj2 == null || loc == null) return loc == locObj2;
|
||||
Location loc2 = (Location) locObj2;
|
||||
@Override
|
||||
public boolean matches(Object object) {
|
||||
if (object == null || location == null) {
|
||||
return location == object;
|
||||
}
|
||||
Location location2 = (Location) object;
|
||||
|
||||
return loc.hasAccuracy() == loc2.hasAccuracy()
|
||||
&& (!loc.hasAccuracy() || loc.getAccuracy() == loc2.getAccuracy())
|
||||
&& loc.hasAltitude() == loc2.hasAltitude()
|
||||
&& (!loc.hasAltitude() || loc.getAltitude() == loc2.getAltitude())
|
||||
&& loc.hasBearing() == loc2.hasBearing()
|
||||
&& (!loc.hasBearing() || loc.getBearing() == loc2.getBearing())
|
||||
&& loc.hasSpeed() == loc2.hasSpeed()
|
||||
&& (!loc.hasSpeed() || loc.getSpeed() == loc2.getSpeed())
|
||||
&& loc.getLatitude() == loc2.getLatitude()
|
||||
&& loc.getLongitude() == loc2.getLongitude()
|
||||
&& loc.getTime() == loc2.getTime();
|
||||
return location.hasAccuracy() == location2.hasAccuracy()
|
||||
&& (!location.hasAccuracy() || location.getAccuracy() == location2.getAccuracy())
|
||||
&& location.hasAltitude() == location2.hasAltitude()
|
||||
&& (!location.hasAltitude() || location.getAltitude() == location2.getAltitude())
|
||||
&& location.hasBearing() == location2.hasBearing()
|
||||
&& (!location.hasBearing() || location.getBearing() == location2.getBearing())
|
||||
&& location.hasSpeed() == location2.hasSpeed()
|
||||
&& (!location.hasSpeed() || location.getSpeed() == location2.getSpeed())
|
||||
&& location.getLatitude() == location2.getLatitude()
|
||||
&& location.getLongitude() == location2.getLongitude()
|
||||
&& location.getTime() == location2.getTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public void appendTo(StringBuffer buffer) {
|
||||
buffer.append("locEq(");
|
||||
buffer.append(loc);
|
||||
buffer.append("locationEq(");
|
||||
buffer.append(location);
|
||||
buffer.append(")");
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
private void fillLocations(Location... locs) {
|
||||
assertTrue(locs.length < 90);
|
||||
for (int i = 0; i < locs.length; i++) {
|
||||
Location location = locs[i];
|
||||
/**
|
||||
* Fills the locations.
|
||||
*
|
||||
* @param locations the locations
|
||||
*/
|
||||
private void fillLocations(Location... locations) {
|
||||
assertTrue(locations.length < 90);
|
||||
for (int i = 0; i < locations.length; i++) {
|
||||
Location location = locations[i];
|
||||
location.setLatitude(i + 1);
|
||||
location.setLongitude(i + 1);
|
||||
location.setTime(i + 1000);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.google.android.apps.mytracks.endtoendtest;
|
||||
|
||||
import com.google.android.apps.mytracks.TrackListActivity;
|
||||
import com.google.android.apps.mytracks.util.FileUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
@@ -73,8 +74,10 @@ public class DeleteTest extends ActivityInstrumentationTestCase2<TrackListActivi
|
||||
EndToEndTestUtils.SOLO
|
||||
.clickOnText(String.format(activityMyTracks.getString(R.string.menu_save_format),
|
||||
EndToEndTestUtils.GPX.toUpperCase()));
|
||||
assertTrue(EndToEndTestUtils.SOLO.waitForText(activityMyTracks
|
||||
.getString(R.string.external_storage_save_error_no_track)));
|
||||
String totalTracks = activityMyTracks.getResources().getQuantityString(R.plurals.tracks, 0, 0);
|
||||
String directoryName = FileUtils.buildExternalDirectoryPath(EndToEndTestUtils.GPX);
|
||||
String message = activityMyTracks.getString(R.string.save_error, 0, totalTracks, directoryName);
|
||||
assertTrue(EndToEndTestUtils.SOLO.waitForText(message));
|
||||
EndToEndTestUtils
|
||||
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
|
||||
}
|
||||
|
||||
+23
-12
@@ -87,16 +87,14 @@ public class ExportAndImportTest extends ActivityInstrumentationTestCase2<TrackL
|
||||
EndToEndTestUtils.SOLO
|
||||
.clickOnText(String.format(activityMyTracks.getString(R.string.menu_save_format),
|
||||
EndToEndTestUtils.GPX.toUpperCase()));
|
||||
EndToEndTestUtils.SOLO.waitForText(activityMyTracks
|
||||
.getString(R.string.external_storage_save_success));
|
||||
EndToEndTestUtils.SOLO.waitForText(getSaveSuccessMessage(1, EndToEndTestUtils.GPX));
|
||||
EndToEndTestUtils
|
||||
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
|
||||
EndToEndTestUtils.deleteAllTracks();
|
||||
|
||||
// Import this tracks.
|
||||
EndToEndTestUtils.findMenuItem(activityMyTracks.getString(R.string.menu_import), true);
|
||||
EndToEndTestUtils.SOLO.waitForText(activityMyTracks.getString(
|
||||
R.string.external_storage_import_success_count).split("%")[0]);
|
||||
EndToEndTestUtils.SOLO.waitForText(getImportSuccessMessage(1, EndToEndTestUtils.GPX));
|
||||
// Check import tracks should be equal with the sum of trackNumber and
|
||||
// gpxFilesNumber;
|
||||
EndToEndTestUtils
|
||||
@@ -171,9 +169,7 @@ public class ExportAndImportTest extends ActivityInstrumentationTestCase2<TrackL
|
||||
|
||||
// No file to imported.
|
||||
EndToEndTestUtils.findMenuItem(activityMyTracks.getString(R.string.menu_import), true);
|
||||
EndToEndTestUtils.SOLO.waitForText(activityMyTracks.getString(
|
||||
R.string.import_no_file,
|
||||
FileUtils.buildExternalDirectoryPath(EndToEndTestUtils.GPX)));
|
||||
EndToEndTestUtils.SOLO.waitForText(getImportErrorMessage(0, 0, EndToEndTestUtils.GPX));
|
||||
EndToEndTestUtils
|
||||
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
|
||||
|
||||
@@ -182,8 +178,7 @@ public class ExportAndImportTest extends ActivityInstrumentationTestCase2<TrackL
|
||||
EndToEndTestUtils.SOLO
|
||||
.clickOnText(String.format(activityMyTracks.getString(R.string.menu_save_format),
|
||||
EndToEndTestUtils.GPX.toUpperCase()));
|
||||
EndToEndTestUtils.SOLO.waitForText(activityMyTracks
|
||||
.getString(R.string.external_storage_save_success));
|
||||
EndToEndTestUtils.SOLO.waitForText(getSaveSuccessMessage(1, EndToEndTestUtils.GPX));
|
||||
EndToEndTestUtils
|
||||
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
|
||||
// Check export file.
|
||||
@@ -217,8 +212,7 @@ public class ExportAndImportTest extends ActivityInstrumentationTestCase2<TrackL
|
||||
EndToEndTestUtils.SOLO
|
||||
.clickOnText(String.format(activityMyTracks.getString(R.string.menu_save_format),
|
||||
EndToEndTestUtils.KML.toUpperCase()));
|
||||
EndToEndTestUtils.SOLO.waitForText(activityMyTracks
|
||||
.getString(R.string.external_storage_save_success));
|
||||
EndToEndTestUtils.SOLO.waitForText(getSaveSuccessMessage(2, EndToEndTestUtils.KM));
|
||||
EndToEndTestUtils
|
||||
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
|
||||
// Check export files.
|
||||
@@ -231,5 +225,22 @@ public class ExportAndImportTest extends ActivityInstrumentationTestCase2<TrackL
|
||||
EndToEndTestUtils.SOLO.finishOpenedActivities();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
|
||||
private String getSaveSuccessMessage(int count, String type) {
|
||||
String tracks = activityMyTracks.getResources().getQuantityString(R.plurals.tracks, count, count);
|
||||
String directoryName = FileUtils.buildExternalDirectoryPath(type);
|
||||
return activityMyTracks.getString(R.string.save_success, tracks, directoryName);
|
||||
}
|
||||
|
||||
private String getImportSuccessMessage(int count, String type) {
|
||||
String files = activityMyTracks.getResources().getQuantityString(R.plurals.files, count, count);
|
||||
String directoryName = FileUtils.buildExternalDirectoryPath(type);
|
||||
return activityMyTracks.getString(R.string.import_success, files, directoryName);
|
||||
}
|
||||
|
||||
private String getImportErrorMessage(int count, int total, String type) {
|
||||
String files = activityMyTracks.getResources().getQuantityString(R.plurals.files, total, total);
|
||||
String directoryName = FileUtils.buildExternalDirectoryPath(type);
|
||||
return activityMyTracks.getString(R.string.import_error, count, files, directoryName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ public class SettingTest extends ActivityInstrumentationTestCase2<TrackListActiv
|
||||
EndToEndTestUtils.SOLO.clickOnText(activityMyTracks.getString(R.string.settings_backup));
|
||||
EndToEndTestUtils.SOLO.clickOnText(activityMyTracks.getString(R.string.settings_backup_now));
|
||||
assertTrue(EndToEndTestUtils.SOLO.waitForText(
|
||||
activityMyTracks.getString(R.string.external_storage_save_success), 0,
|
||||
activityMyTracks.getString(R.string.settings_backup_now_success), 0,
|
||||
EndToEndTestUtils.SUPER_LONG_WAIT_TIME));
|
||||
instrumentation.waitForIdleSync();
|
||||
|
||||
@@ -264,7 +264,7 @@ public class SettingTest extends ActivityInstrumentationTestCase2<TrackListActiv
|
||||
EndToEndTestUtils
|
||||
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
|
||||
assertTrue(EndToEndTestUtils.SOLO.waitForText(
|
||||
activityMyTracks.getString(R.string.restore_success), 0,
|
||||
activityMyTracks.getString(R.string.settings_backup_restore_success), 0,
|
||||
EndToEndTestUtils.SUPER_LONG_WAIT_TIME));
|
||||
// Check restore track.
|
||||
assertTrue(EndToEndTestUtils.SOLO.searchText(EndToEndTestUtils.trackName));
|
||||
|
||||
Reference in New Issue
Block a user